├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── tagged-release.yml ├── .gitignore ├── README.md ├── build.js ├── controllers ├── experimentsController.js ├── gameController.js ├── presenceController.js ├── profileController.js └── webController.js ├── index.js ├── nodemon.json ├── package-lock.json ├── package.json ├── public ├── css │ ├── Asadera-regular.otf │ ├── hierarchy-select.min.css │ └── style.css ├── experimental.html ├── icon.ico ├── images │ └── Vulx-Puple-Blue.ico ├── index.html ├── js │ ├── app.js │ ├── hierarchy.js │ ├── slip.js │ ├── vulx.load.js │ ├── vulx.localization.js │ ├── vulx.profile.js │ ├── vulx.request.friends.js │ ├── vulx.request.reset.js │ ├── vulx.request.session.js │ ├── vulx.request.settings.js │ ├── vulx.search.js │ └── vulx.welcome.js ├── json │ └── ranks.json ├── locales │ ├── en │ │ └── default.json │ └── ru │ │ └── default.json └── welcome.html ├── routes ├── experimentsRouter.js ├── gameRouter.js ├── index.js ├── profileRouter.js └── webRouter.js ├── utils ├── FriendHelper.js ├── LookupAPI.js ├── SystemMessageHelper.js ├── ValorantAPI.js ├── axiosHelper.js ├── catchAsync.js ├── configHelper.js ├── discordHelper.js ├── jsonHelper.js ├── lockfile.js ├── logger.js └── meHelper.js ├── valorant.json └── vulx-vision.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: aquaplaysyt 2 | patreon: aquaplaysyt -------------------------------------------------------------------------------- /.github/workflows/tagged-release.yml: -------------------------------------------------------------------------------- 1 | name: "tagged-release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | tagged-release: 10 | name: "Tagged Release" 11 | runs-on: windows-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build with Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: 'npm' 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - uses: "marvinpinto/action-automatic-releases@latest" 27 | with: 28 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 29 | prerelease: false 30 | files: | 31 | build/Vulx.exe 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | build/ 133 | cfg/ 134 | licensekey.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | [![Contributors][contributors-shield]][contributors-url] 5 | [![Forks][forks-shield]][forks-url] 6 | [![Stargazers][stars-shield]][stars-url] 7 | [![Issues][issues-shield]][issues-url] 8 | [![Download][release-shield]][release-url] 9 | [![Discord][discord-shield]][discord-url] 10 | 11 | 12 | 13 | 14 | 15 |

Vulx - Profile Editor

16 | 17 |

18 | An application made to enhance your Valorant experience! 19 |
20 |

21 |

22 | Issues 23 | · 24 | Discord 25 | · 26 | YouTube 27 |

28 |
29 | 30 | # About The Project 31 | 32 | Vulx is a profile editor made for Valorant that creates a dedicated dashboard allowing for full customisation within the clients presence service. Built using Node.JS and requiring zero authentication Vulx offers the best features while prioritizing the users experience :smile: 33 | 34 | For any and all help related to Vulx join our [Discord server](https://discord.gg/gHWTGbqZ3y)! 35 | 36 | # Getting Started 37 | 38 | To get started simply head over to [releases](https://github.com/vulxdev/Vulx/releases) and download the latest version of Vulx. We'll be updating Vulx periodically so make sure to check back to get the latest cool features! 39 | 40 | Our Discord has a dedicated channel for support and help, if you have any issues or questions make sure to join and ask away! 41 | 42 | # License 43 | 44 | Vulx is under exclusive copyright and is not to be redistributed without the owners permission. If you wish to use anything from Vulx please contact us [here](mailto:xexstudiosinc@gmail.com) to get written permission. 45 | 46 | # Contributing 47 | 48 | If you wish to contribute to Vulx you can, however please note that since Vulx is under exclusive copyright protection any contributions will become the property of Vulx and will be used at the discretion of the owners. 49 | 50 | If that's okay with you then feel free to make a pull request and we'll review it as soon as possible! 51 | 52 | # Translating 53 | 54 | If you can't code but still want to contribute to Vulx you can help us translate the application into your native language! 55 | 56 | To help us with translations you can go [here](https://crowdin.com/project/vulx) and help by translating Vulx into your language. 57 | 58 | # Contact 59 | 60 | To get into direct contact with us you can use our [email](mailto:xexstudiosinc@gmail.com) or talk to us on [Discord](https://discord.gg/gHWTGbqZ3y). 61 | 62 | You'll get faster responses on Discord so if you'd like to make a request or report a bug please use that! 63 | 64 | # Acknowledgements 65 | 66 | * [Aqua](https://github.com/AquaPlaysYT) - Project Owner 67 | * [Syfe](https://github.com/ItsSyfe) - Project Owner 68 | * [Discreet](https://github.com/discreet-pc) - Beta tester and very helpful for ideas 69 | * [NotOfficer](https://github.com/NotOfficer) - Providing us with the extremely useful [Valorant-API](https://valorant-api.com/) 70 | 71 | --- 72 | 73 |

⚠️ FOR RIOT GAMES EMPLOYEES ⚠️

74 |

IF YOU HAVE ANY ISSUES WITH VULX PLEASE CONTACT US HERE BEFORE DOING ANYTHING TO OUR USERS

75 |

OUR MAIN PRIORITY IS OUR USERS SAFETY!

76 | 77 |

⚠️ THIS PROJECT IS NOW BANNABLE ⚠️

78 |

ANY ATTEMPT TO USE THIS PROJECT MAY RESULT IN A 12-14 DAY BAN!

79 |

PLEASE BEWARE!

80 | 81 | 82 | [contributors-shield]: https://img.shields.io/github/contributors/vulxdev/Vulx.svg?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 83 | [contributors-url]: https://github.com/vulxdev/Vulx/graphs/contributors 84 | [crowdin-shield]: https://img.shields.io/badge/dynamic/json?color=blue&label=localized&style=for-the-badge&query=%24.progress..data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15478298-541478.json&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 85 | [crowdin-url]: https://crowdin.com/project/vulx 86 | [forks-shield]: https://img.shields.io/github/forks/vulxdev/Vulx.svg?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 87 | [forks-url]: https://github.com/vulxdev/Vulx/network/members 88 | [stars-shield]: https://img.shields.io/github/stars/vulxdev/Vulx.svg?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 89 | [stars-url]: https://github.com/vulxdev/Vulx/stargazers 90 | [issues-shield]: https://img.shields.io/github/issues/vulxdev/Vulx.svg?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 91 | [issues-url]: https://github.com/vulxdev/Vulx/issues 92 | [discord-shield]: https://img.shields.io/discord/551098163172212748?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 93 | [discord-url]: https://discord.gg/gHWTGbqZ3y 94 | [release-shield]: https://img.shields.io/github/downloads/vulxdev/Vulx/total?style=for-the-badge&color=blue&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB+FBMVEUAAAAMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGScMGCQMFyEMFyIMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCQMGCMSL2ITMWgMFyIMFyERKFITL2QLFiAePH08cd8TJUoLFyEPHjg2YcsiQ5AlOnpxpv9DZL8PGzMMGCk3TqJ2oP8tRI0LFiEqN3qErP+Dq/85RpoMGCcLFyIrNHmDl/iWr/80QIwKFiEKFyAwNXqQqP+dv/+Ck/YtMHMfJFV9eueyt/+nqf87PYwxLnKYnP6ovP+nuf98d+MhIlELGCIVGzpwXMu+rf/Etf+4pP9COo0KFyELGCMVGzdsWMSvrv+yuP+vr/9wWccWGjcPGCpdQKnFmv/Vrv/Urf+jevAqJV4kH06RbeG+s/++tv+1oP9dPqAiHU1oPbd9UcF9UcB8T8JELn4MGCUKFyI+J2+xgfXKs/+2ifhKKn0NFygOFykNFyZjM5bLk//Vsv/Wsf+wb+gzH1cKGCEWFzKMQrvUhv7Ui/zUgv+YOsodGDkhGT1GH2hHIGlFHmcaGTQJGCD///8ge9PiAAAAMXRSTlMAABVfq9vv7timWBEEU8T49r1KAgqE9PB4BgWG/fn28BrLavu66fzrv3Ef0WIIk/0PUBjJhQAAAAFiS0dEp8C3KwMAAAAHdElNRQfmCQ4MLR9FPukDAAABWElEQVQY02NgYAQCJmYWVjZ2Dk4ubiCHgQEkxsPLx28IBgKCQsIgUUZGEVExQzgQl5AEijJKScsgxAyNZCWEgYJccsYmpjAxM3MLeSFGBgVFSytrG1uImKmdvYOtIDeDkrKlo5OzixFYr6ubu4enABeDiqGll7ePrx9I0D8gMCg4xJCTQdUwNCw8IjLK39DQNjomNi4+xJCDQc0wNCExKTkl1TYtPSMzKzsn15AdKJiXX1BYVFxSWlZeUVlVXWNryMagDtRWW1ff0NjU3NLa1t7RaWjIyqABtKCru6e3t69/wsSJE0H2sTBoqgGpSZOnTJ02fcZMsGP5mRm0tIF03qzZc+bOm78gDyTIx8TAqCMLZKQtXLR48ZKlIEExXqDfdfVA0nnLgAAkpi/KAxRkNNCTRYSSgLQIKOgYGXV1tNUgQsqKXFKMkFBmZNTS1FBXU1NVUVIA8RgAaZ9afHHbzysAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDktMTRUMTI6NDU6MTMrMDA6MDD5+UOTAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA5LTE0VDEyOjQ1OjEzKzAwOjAwiKT7LwAAAABJRU5ErkJggg== 95 | [release-url]: https://github.com/vulxdev/Vulx/releases 96 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // build.js 2 | const exe = require('@angablue/exe'); 3 | 4 | const build = exe({ 5 | entry: './index.js', 6 | out: './build/Vulx.exe', 7 | pkg: ['--config', 'package.json'], 8 | version: '1.0.0', 9 | target: 'node17-win-x64', 10 | icon: './public/icon.ico', 11 | properties: { 12 | FileDescription: 'Vulx', 13 | ProductName: 'Vulx', 14 | LegalCopyright: 'Vulx Team - All Rights Reserved', 15 | OriginalFilename: 'Vulx.exe' 16 | } 17 | }); 18 | 19 | build.then(() => console.log('Build completed!')); -------------------------------------------------------------------------------- /controllers/experimentsController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const httpStatus = require('http-status'); 10 | const path = require('path'); 11 | 12 | // helper definitions 13 | const catchAsync = require('../utils/catchAsync'); 14 | const Logger = require('../utils/Logger'); 15 | const DiscordRPC = require("../utils/discordHelper"); 16 | const meHelper = require('../utils/meHelper'); 17 | 18 | const updateExperiments = catchAsync(async (req, res) => { 19 | const valConfig = await meHelper.getValorantJson(); 20 | 21 | valConfig.queueId = req.body.status; 22 | valConfig.competitiveTier = req.body.rank; 23 | valConfig.leaderboardPosition = req.body.position; 24 | valConfig.accountLevel = req.body.level; 25 | valConfig.partyOwnerMatchScoreAllyTeam = req.body.ally; 26 | valConfig.partyOwnerMatchScoreEnemyTeam = req.body.enemy; 27 | 28 | await DiscordRPC.refreshActivity(); 29 | 30 | await meHelper.updateRequest(valConfig); 31 | 32 | await res.sendFile(path.join(__dirname, '../public/experimental.html')); 33 | }); 34 | 35 | const currentExperiments = catchAsync(async (req, res) => { 36 | const data = { 37 | leagueToggle: true, 38 | } 39 | 40 | Logger.debug(`Sending current experiments to client, ${JSON.stringify(data)}`); 41 | 42 | res.status(httpStatus.OK).send(data); 43 | }); 44 | 45 | module.exports = { 46 | updateExperiments, 47 | currentExperiments 48 | } -------------------------------------------------------------------------------- /controllers/gameController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const httpStatus = require('http-status'); 10 | 11 | // helper definitions 12 | const catchAsync = require('../utils/catchAsync'); 13 | const SystemMessageHelper = require('../utils/SystemMessageHelper'); 14 | 15 | const sendSystemMessage = catchAsync(async (req, res) => { 16 | await SystemMessageHelper.sendSystemMessage(req.body.message); 17 | res.status(httpStatus.OK).send("Message sent"); 18 | }); 19 | 20 | module.exports = { 21 | sendSystemMessage 22 | } -------------------------------------------------------------------------------- /controllers/presenceController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const httpStatus = require('http-status'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | 13 | // helper definitions 14 | const catchAsync = require('../utils/catchAsync'); 15 | const Logger = require('../utils/Logger'); 16 | const DiscordRPC = require("../utils/discordHelper"); 17 | const meHelper = require('../utils/meHelper'); 18 | const ConfigHelper = require('../utils/ConfigHelper'); 19 | 20 | const updatePresence = catchAsync(async (req, res) => { 21 | let flag = req.body.flag; 22 | const valConfig = await ConfigHelper.getValorantConfig(); 23 | 24 | if (flag & 1) { 25 | if(req.body.status) 26 | valConfig.queueId = req.body.status; 27 | } 28 | if (flag & 2) { 29 | valConfig.competitiveTier = req.body.rank; 30 | } 31 | if (flag & 4) { 32 | valConfig.leaderboardPosition = req.body.position; 33 | } 34 | if (flag & 8) { 35 | valConfig.accountLevel = req.body.level; 36 | } 37 | if (flag & 16) { 38 | valConfig.partyOwnerMatchScoreAllyTeam = req.body.ally; 39 | } 40 | if (flag & 32) { 41 | valConfig.partyOwnerMatchScoreEnemyTeam = req.body.enemy; 42 | } 43 | if (flag & 64) { 44 | valConfig.playerTitleId = req.body.playerTitleId; 45 | } 46 | 47 | Logger.debug(`Updating presence :: Flag: ${flag} | ${JSON.stringify(valConfig)}`); 48 | 49 | await DiscordRPC.refreshActivity(); 50 | await meHelper.updateRequest(valConfig); 51 | await res.status(httpStatus.OK).send(); 52 | }); 53 | 54 | const currentSettings = catchAsync(async (req, res) => { 55 | const valConfig = await ConfigHelper.getValorantConfig(); 56 | 57 | let status; 58 | if(valConfig.sessionLoopState == "INGAME") status = "online"; 59 | else if(valConfig.partyId == "" || valConfig.partyId == null) status = "offline"; 60 | else if(valConfig.sessionLoopState !== "MENUS" && valConfig.sessionLoopState !== "INGAME") status = "stream"; 61 | else if(valConfig.isValid == false) status = "dnd"; 62 | else if(valConfig.sessionLoopState == "MENUS" && valConfig.isIdle == true) status = "away"; 63 | else if (valConfig.sessionLoopState == "MENUS" && valConfig.partyId == "727") status = "available"; 64 | 65 | const data = { 66 | queueId: valConfig.queueId, 67 | competitiveTier: valConfig.competitiveTier, 68 | leaderboardPosition: valConfig.leaderboardPosition, 69 | accountLevel: valConfig.accountLevel, 70 | partyOwnerMatchScoreAllyTeam: valConfig.partyOwnerMatchScoreAllyTeam, 71 | partyOwnerMatchScoreEnemyTeam: valConfig.partyOwnerMatchScoreEnemyTeam, 72 | playerCardId: valConfig.playerCardId, 73 | playerTitleId: valConfig.playerTitleId, 74 | status: status 75 | } 76 | 77 | Logger.debug(`Fufilling current settings request :: ${JSON.stringify(data)}`); 78 | 79 | res.status(httpStatus.OK).send(data); 80 | }); 81 | 82 | module.exports = { 83 | updatePresence, 84 | currentSettings 85 | } -------------------------------------------------------------------------------- /controllers/profileController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const httpStatus = require('http-status'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | 13 | // helper definitions 14 | const catchAsync = require('../utils/catchAsync'); 15 | const AxiosHelper = require('../utils/AxiosHelper'); 16 | const Lockfile = require('../utils/lockfile'); 17 | const ConfigHelper = require('../utils/configHelper'); 18 | const FriendHelper = require('../utils/FriendHelper'); 19 | const meHelper = require('../utils/meHelper'); 20 | const Logger = require('../utils/Logger'); 21 | const ValorantAPI = require('../utils/ValorantAPI'); 22 | 23 | const updateStatus = catchAsync(async (req, res) => { 24 | const valConfig = await ConfigHelper.getValorantConfig(); 25 | 26 | valConfig.sessionLoopState = "INGAME"; 27 | valConfig.partyId = "727"; 28 | valConfig.isValid = true; 29 | valConfig.isIdle = false; 30 | 31 | switch (req.body.status) { 32 | case "offline": 33 | valConfig.sessionLoopState = "MENUS"; 34 | valConfig.partyId = undefined; 35 | break; 36 | case "stream": 37 | valConfig.sessionLoopState = "WYSI"; 38 | break; 39 | case "dnd": 40 | valConfig.isValid = false; 41 | valConfig.sessionLoopState = "MENUS"; 42 | break; 43 | case "away": 44 | valConfig.isIdle = true; 45 | valConfig.sessionLoopState = "MENUS"; 46 | case "available": 47 | valConfig.sessionLoopState = "MENUS"; 48 | default: 49 | break; 50 | } 51 | 52 | Logger.debug(`Status updated :: ${JSON.stringify(valConfig)}`); 53 | 54 | await meHelper.updateRequest(valConfig); 55 | await res.status(httpStatus.OK).send(); 56 | }); 57 | 58 | const getRequestsCount = catchAsync(async (req, res) => { 59 | const response = await (await AxiosHelper.getVulxAxios()).get("/chat/v3/friendrequests"); 60 | const returnJson = { 61 | count: response.data.requests.length, 62 | }; 63 | 64 | Logger.debug(`Friend requests count :: ${JSON.stringify(returnJson)}`); 65 | res.status(httpStatus.OK).send(returnJson); 66 | }); 67 | 68 | const timePlaying = catchAsync(async (req, res) => { 69 | const response = await (await AxiosHelper.getVulxAxios()).get("/telemetry/v1/application-start-time"); 70 | const returnJson = { 71 | time: response.data, 72 | }; 73 | Logger.debug(`Game Telemetry :: ${JSON.stringify(returnJson)}`); 74 | 75 | res.status(httpStatus.OK).send(returnJson); 76 | }); 77 | 78 | const userSession = catchAsync(async (req, res) => { 79 | const config = await ConfigHelper.getVulxConfig(); 80 | 81 | const userInfo = await ValorantAPI.getUserInfo(); 82 | const returnJson = { 83 | session: userInfo, 84 | config: config, 85 | port: Lockfile.port, 86 | password: Buffer.from(`riot:${Lockfile.password}`).toString('base64') 87 | }; 88 | Logger.debug(`Session info :: ${JSON.stringify(returnJson)}`); 89 | 90 | res.status(httpStatus.OK).send(returnJson); 91 | }); 92 | 93 | const updateSettings = catchAsync(async (req, res) => { 94 | const config = await ConfigHelper.getVulxConfig(); 95 | 96 | Logger.debug(`Updated settings: 97 | Experimental Features: ${config.experimental} --> ${req.body.experimentalFeatures} 98 | Discord RPC: ${config.discordRpc} --> ${req.body.discordRpc} 99 | First Launch: ${config.firstLaunch} --> ${req.body.firstLaunch} 100 | Web ToolTips: ${config.webTooltips} --> ${req.body.webTooltips}`); 101 | 102 | switch (req.body.updateType) { 103 | case "settingsIndex": 104 | config.experimental = req.body.experimentalFeatures === "true" ? true : false; 105 | config.discordRpc = req.body.discordRpc === "true" ? true : false; 106 | config.webTooltips = req.body.webTooltips === "true" ? true : false; 107 | break; 108 | case "settingsWelcome": 109 | config.firstLaunch = req.body.firstLaunch; 110 | config.discordRpc = req.body.data.discordRpc === "true" ? true : false; 111 | config.experimental = req.body.data.testFeatures === "true" ? true : false; 112 | break; 113 | } 114 | 115 | Logger.debug(`Updated Vulx settings :: ${JSON.stringify(config)}`); 116 | 117 | ConfigHelper.vulxConfig = config; 118 | await ConfigHelper.saveConfig(); 119 | 120 | res.redirect("/dashboard"); 121 | }); 122 | 123 | const resetAccount = catchAsync(async (req, res) => { 124 | if(req.body.resetAccount == true) { 125 | await ConfigHelper.resetConfig(); 126 | Logger.debug("Resetting account"); 127 | res.status(httpStatus.OK).send(); 128 | } 129 | res.status(httpStatus.IM_A_TEAPOT).send(); 130 | }); 131 | 132 | const getFriends = catchAsync(async (req, res) => { 133 | const friends = await FriendHelper.getFriends(); 134 | const presences = await FriendHelper.getPresences(); 135 | 136 | const data = { 137 | friends: friends, 138 | onlineFriends: presences 139 | } 140 | 141 | Logger.debug(`Fufilled friends request :: ${JSON.stringify(data)}`); 142 | res.status(httpStatus.OK).send(data); 143 | }); 144 | 145 | module.exports = { 146 | userSession, 147 | timePlaying, 148 | getRequestsCount, 149 | updateSettings, 150 | updateStatus, 151 | resetAccount, 152 | getFriends 153 | }; -------------------------------------------------------------------------------- /controllers/webController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const path = require('path'); 10 | 11 | // helper definitions 12 | const catchAsync = require('../utils/catchAsync'); 13 | 14 | const dashboard = catchAsync(async (req, res) => { 15 | await res.sendFile(path.join(__dirname, '../public/index.html')); 16 | }); 17 | 18 | const setup = catchAsync(async (req, res) => { 19 | await res.sendFile(path.join(__dirname, '../public/welcome.html')); 20 | }); 21 | 22 | const info = catchAsync(async (req, res) => { 23 | await res.sendFile(path.join(__dirname, '../public/info.html')); 24 | }); 25 | 26 | const user = catchAsync(async (req, res) => { 27 | await res.sendFile(path.join(__dirname, `../public/user.html?puuid=${req.params.puuid}`)); 28 | }); 29 | 30 | module.exports = { 31 | dashboard, 32 | setup, 33 | info, 34 | user, 35 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const path = require('path'); 10 | const open = require('open'); 11 | const express = require('express'); 12 | 13 | // local imports 14 | const DiscordRPC = require("./utils/discordHelper"); 15 | const Logger = require('./utils/Logger'); 16 | const ConfigHelper = require('./utils/ConfigHelper'); 17 | const routes = require('./routes'); 18 | const MeHelper = require('./utils/meHelper'); 19 | const SystemMessageHelper = require('./utils/SystemMessageHelper'); 20 | const ValorantAPI = require('./utils/ValorantAPI'); 21 | 22 | // definitions 23 | const isDevelopment = process.env.NODE_ENV === 'development'; 24 | const port = 80; 25 | const link = `http://127.0.0.1:${port}/`; 26 | 27 | // express definition 28 | const app = express(); 29 | app.use(express.json()); 30 | app.use(express.static(path.join(__dirname, 'public'))); 31 | app.use(express.urlencoded({ extended: true })); 32 | app.use('/', routes); 33 | 34 | MeHelper.init(); 35 | DiscordRPC.init(); 36 | 37 | (async () => { 38 | const valorantConfig = await ConfigHelper.getValorantConfig(); 39 | 40 | // welcome message 41 | await SystemMessageHelper.sendSystemMessage(`◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤`); 42 | await SystemMessageHelper.sendSystemMessage(`♡ Welcome to Vulx ${await ValorantAPI.getGameName()}`); 43 | await SystemMessageHelper.sendSystemMessage(`♡ Your current rank is ${DiscordRPC.rankIdToName[valorantConfig.competitiveTier]}`); 44 | await SystemMessageHelper.sendSystemMessage(`♡ For support join discord.gg/vulx`); 45 | await SystemMessageHelper.sendSystemMessage(`♡ Made with love by Aqua & Syfe`); 46 | await SystemMessageHelper.sendSystemMessage(`◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤`); 47 | 48 | process.title = "Vulx"; 49 | })(); 50 | 51 | app.get("/", (req, res) => { 52 | res.set({ "Allow-access-Allow-Origin": "*" }); 53 | res.redirect("/setup"); 54 | }); 55 | 56 | app.listen(port, () => { 57 | Logger.info('Vulx has finished loading! Welcome to Vulx.') 58 | Logger.debug(`Vulx initialized on port ${port}`); 59 | if(process.pkg) 60 | open(link); 61 | }); -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["index.js", "utils/", "public/", "routes/", "controllers/"], 3 | "ext": "js, css" 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vulx-local", 3 | "version": "1.0.0", 4 | "description": "Local client that allows Valorant profile changes.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "SET NODE_ENV=development&& nodemon index.js", 8 | "build": "node build.js" 9 | }, 10 | "pkg": { 11 | "assets": [ 12 | "public/**/*" 13 | ] 14 | }, 15 | "author": "AquaPlays", 16 | "license": "All Rights Reserved", 17 | "dependencies": { 18 | "@angablue/exe": "^1.1.3", 19 | "@forevolve/bootstrap-dark": "^2.1.0", 20 | "axios": "^0.26.1", 21 | "discord-rpc": "^4.0.1", 22 | "eastasianwidth": "^0.2.0", 23 | "emoji-regex": "^10.1.0", 24 | "express": "^4.17.3", 25 | "http-status": "^1.5.1", 26 | "i18next": "^21.10.0", 27 | "nodemon": "^2.0.15", 28 | "open": "^8.4.0", 29 | "portfinder": "^1.0.28", 30 | "string-width": "^5.1.2", 31 | "winston": "^3.8.2", 32 | "winston-daily-rotate-file": "^4.7.1" 33 | }, 34 | "devDependencies": { 35 | "pkg": "^5.6.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/css/Asadera-regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulxdev/Vulx/efec18f4e0d33ca588a5a445fbbba0f3cd35ba07/public/css/Asadera-regular.otf -------------------------------------------------------------------------------- /public/css/hierarchy-select.min.css: -------------------------------------------------------------------------------- 1 | .hierarchy-select.dropdown .hs-searchbox{padding:0 5px 4px}.hierarchy-select.dropdown .dropdown-menu a[data-level='2']{padding-left:40px}.hierarchy-select.dropdown .dropdown-menu a[data-level='3']{padding-left:60px}.hierarchy-select.dropdown .dropdown-menu a[data-level='4']{padding-left:80px}.hierarchy-select.dropdown .dropdown-menu a[data-level='5']{padding-left:100px}.hierarchy-select.dropdown .dropdown-menu a[data-level='6']{padding-left:120px}.hierarchy-select.dropdown .dropdown-menu a[data-level='7']{padding-left:140px}.hierarchy-select.dropdown .dropdown-menu a[data-level='8']{padding-left:160px}.hierarchy-select.dropdown .dropdown-menu a[data-level='9']{padding-left:180px}.hierarchy-select.dropdown .dropdown-menu a[data-level='10']{padding-left:200px} 2 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* Copyright (C) Vulx - All Rights Reserved 2 | * Unauthorized copying of this file, via any medium is strictly prohibited 3 | * Proprietary and confidential 4 | * Written by Vulx Team , 2022 5 | */ 6 | 7 | body { 8 | margin: 0; 9 | font-family: sans-serif; 10 | line-height: 1.5; 11 | background-color: #101116 !important; 12 | } 13 | 14 | a { 15 | font-family: sans-serif !important; 16 | } 17 | 18 | .info { 19 | box-shadow: none !important; 20 | } 21 | 22 | .nav-link { 23 | padding-left: 10px !important; 24 | padding-right: 10px !important; 25 | } 26 | 27 | .profile-themes-container { 28 | display: flex; 29 | justify-content: center; 30 | } 31 | 32 | .textBox { 33 | width: 25%; 34 | padding: 10px; 35 | } 36 | 37 | .textLabel { 38 | margin-bottom: -2px; 39 | } 40 | 41 | .textBoxCont { 42 | display: flex; 43 | justify-content: center; 44 | } 45 | 46 | .listCont { 47 | width: 100%; 48 | padding: 0px !important; 49 | } 50 | 51 | /* these are special */ 52 | .slip-reordering { 53 | box-shadow: 0 2px 10px rgba(0,0,0,0.45); 54 | } 55 | 56 | .slip-swiping-container { 57 | overflow-x: hidden; 58 | } 59 | 60 | .slippylist li { 61 | user-select: none; 62 | -moz-user-select: none; 63 | -webkit-user-select: none; 64 | cursor: default; 65 | } 66 | 67 | .slippylist li { 68 | display: block; 69 | position: relative; 70 | background: #14151c; 71 | margin-bottom: 5px; padding: 1px 1em; 72 | border-radius: 3px; 73 | max-width: 100%; 74 | line-height: 3; 75 | vertical-align: middle; 76 | } 77 | 78 | .slippyList { 79 | width: 100%; 80 | padding: 0; 81 | } 82 | 83 | .draggable { 84 | transition: transform 2s; 85 | } 86 | 87 | .slippylist .instant::after { 88 | content: " \2261"; 89 | } 90 | .slippylist .instant { 91 | float: right; 92 | } 93 | 94 | .profileBanner { 95 | background-color: #14151c; 96 | height: 505px; 97 | transition: height 0.1s ease-in-out; 98 | border-radius: 15px; 99 | box-shadow: 5px 5px #0c0c0e; 100 | /*overflow-x: hidden;*/ 101 | } 102 | 103 | .valorantBanner { 104 | height: 108px; 105 | position: absolute; 106 | left: 0px; 107 | border-radius: 15px; 108 | -webkit-filter: brightness(100%); 109 | transition-delay:1s; 110 | -webkit-transition: all 1s ease; 111 | -moz-transition: all 1s ease; 112 | -o-transition: all 1s ease; 113 | -ms-transition: all 1s ease; 114 | transition: all 1s ease; 115 | } 116 | 117 | .valorantBanner:hover{ 118 | transition-delay:1s; 119 | -webkit-filter: brightness(70%); 120 | -webkit-transition: all 1s ease; 121 | -moz-transition: all 1s ease; 122 | -o-transition: all 1s ease; 123 | -ms-transition: all 1s ease; 124 | transition: all 1s ease; 125 | } 126 | 127 | .valorantPfp { 128 | position: absolute; 129 | height: 90px; 130 | border-radius: 50%; 131 | border: 4px solid #14151c; 132 | top: 45px; 133 | left: 12px; 134 | } 135 | 136 | .valorantActivity { 137 | position: absolute; 138 | border-radius: 50%; 139 | border: 4px solid #14151c; 140 | height: 20px; 141 | top: 110px; 142 | left: 75px; 143 | } 144 | 145 | .valorantActivityDropdown { 146 | height: 12px; 147 | margin-top: 9px; 148 | padding-left: 10px; 149 | } 150 | 151 | .valorantName { 152 | position: absolute; 153 | top: 135px; 154 | left: 16px; 155 | max-width: 349px; 156 | } 157 | 158 | .valorantTitle { 159 | position: absolute; 160 | top: 171px; 161 | left: 16px; 162 | } 163 | 164 | .valorantRankContainer { 165 | position: absolute; 166 | top: 219px; 167 | left: 16px; 168 | display: flex; 169 | width: 350px; 170 | } 171 | 172 | .valorantRankImg { 173 | height: 45px; 174 | } 175 | 176 | .valorantRankInfo { 177 | width: 100px; 178 | position: absolute; 179 | top: 23px; 180 | font-size: 17px; 181 | left: 50px; 182 | padding-left: 6px; 183 | } 184 | 185 | .valorantRank { 186 | font-size: 19px; 187 | padding-left: 10px; 188 | white-space: nowrap; 189 | overflow: hidden; 190 | text-overflow: ellipsis; 191 | word-break: break-all; 192 | } 193 | 194 | .profileRankDropdown { 195 | background-color: #0c0c0e; 196 | position: absolute; 197 | z-index: 1; 198 | top: 60px; 199 | left: 0px; 200 | width: 352px; 201 | border-radius: 15px; 202 | } 203 | 204 | .profileRankSpecificDropdown { 205 | background-color: #0c0c0e; 206 | position: absolute; 207 | z-index: 1; 208 | top: 0px; 209 | left: 360px; 210 | width: 352px; 211 | border-radius: 15px; 212 | } 213 | 214 | .profileStatusDropdown { 215 | background-color: #0c0c0e; 216 | position: absolute; 217 | z-index: 2; 218 | top: 130px; 219 | left: 86px; 220 | width: 285px; 221 | border-radius: 15px; 222 | } 223 | 224 | .valorantDropdownItem { 225 | padding: 5px; 226 | display: flex; 227 | } 228 | 229 | .valorantDropdownItem:hover { 230 | background-color: #00141e; 231 | } 232 | 233 | .customTooltip { 234 | text-decoration: none !important; 235 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji" !important; 236 | } 237 | 238 | .collapsing { 239 | overflow: hidden !important; 240 | transition: height 0.35s ease 0s !important; 241 | position: absolute !important; 242 | } 243 | 244 | .valorantHighlightTag { 245 | margin-top: -5px; 246 | font-size: 15px; 247 | } 248 | 249 | .valorantInfoContainer { 250 | position: absolute; 251 | top: 283px; 252 | left: 20px; 253 | } 254 | 255 | .valorantMatchContainer { 256 | position: absolute; 257 | top: 387px; 258 | left: 20px; 259 | } 260 | 261 | .valorantMatchInfo { 262 | max-width: 349px; 263 | } 264 | 265 | .vulxAdvertising { 266 | position: absolute; 267 | top: 475px; 268 | left: 20px; 269 | font-size: 16px; 270 | transition: top 0.1s ease-in-out; 271 | } 272 | 273 | .valorantHighlightContainer { 274 | position: absolute; 275 | top: 282px; 276 | } 277 | 278 | .valorantMatchStatusContainer { 279 | position: absolute; 280 | overflow: hidden; 281 | top: 433px; 282 | left: 20px; 283 | max-width: 349px; 284 | width: 349px; 285 | height: auto; 286 | transition: max-height 0.1s ease-in-out; 287 | max-height: 267px; 288 | overflow: scroll; 289 | } 290 | 291 | .flexCont { 292 | display: flex; 293 | width: 300px; 294 | } 295 | 296 | .spacer { 297 | position: absolute; 298 | top: 195px; 299 | left: 16px; 300 | width: 350px; 301 | border: 0.1px solid #333336; 302 | transition: top 0.1s ease-in-out; 303 | } 304 | 305 | .footer { 306 | position: fixed; 307 | left: 0; 308 | bottom: 0; 309 | width: 100%; 310 | background-color: #0c0c0e; 311 | color: white; 312 | text-align: center; 313 | } 314 | 315 | .arrow-left { 316 | position: relative; 317 | top: -50px; 318 | } 319 | 320 | .arrow-left:before, .arrow-left:after { 321 | content: ""; 322 | display: block; 323 | width: 12px; 324 | height: 3px; 325 | background: white; 326 | position: absolute; 327 | top: 17px; 328 | transition: transform .2s; 329 | } 330 | 331 | .arrow-left:before { 332 | right: 10px; 333 | top: 12px; 334 | border-top-left-radius: 10px; 335 | border-bottom-left-radius: 10px; 336 | transform: rotate(45deg); 337 | } 338 | 339 | .arrow-left:after { 340 | top: 19px; 341 | right: 10px; 342 | transform: rotate(-45deg); 343 | } 344 | 345 | .arrow-left.active:before { 346 | transform: rotate(-45deg); 347 | } 348 | 349 | .arrow-left.active:after { 350 | transform: rotate(45deg); 351 | } 352 | 353 | .arrow-down { 354 | position: relative; 355 | top: -50px; 356 | height: 50px; 357 | width: 50px; 358 | margin-left: -50px; 359 | } 360 | 361 | .arrow-downV2 { 362 | position: absolute; 363 | top: 0px !important; 364 | left: 350px !important; 365 | } 366 | 367 | .arrow-down:before, .arrow-down:after { 368 | content: ""; 369 | display: block; 370 | width: 20px; 371 | height: 5px; 372 | background: white; 373 | position: absolute; 374 | top: 20px; 375 | transition: transform .2s; 376 | } 377 | 378 | .arrow-down:before { 379 | right: 21px; 380 | border-top-left-radius: 10px; 381 | border-bottom-left-radius: 10px; 382 | transform: rotate(45deg); 383 | } 384 | 385 | .arrow-down:after { 386 | right: 10px; 387 | transform: rotate(-45deg); 388 | } 389 | 390 | .arrow-down.active:before { 391 | transform: rotate(-45deg); 392 | } 393 | 394 | .arrow-down.active:after { 395 | transform: rotate(45deg); 396 | } 397 | 398 | .wrapper { 399 | display: flex; 400 | height: 100vh; 401 | background: linear-gradient(#0000002e, rgb(0 0 0 / 54%)), url(https://cdn.aquaplays.xyz/profile/sprinkle.svg); 402 | /* background-repeat: no-repeat; */ 403 | background-attachment: fixed; 404 | background-position: center; 405 | } 406 | 407 | .main { 408 | flex-grow: 1; 409 | padding: 20px; 410 | position: relative; 411 | } 412 | 413 | .margin { 414 | margin: 5px; 415 | } 416 | 417 | .navbar-brand { 418 | font-size: 1.5rem !important; 419 | font-weight: bold; 420 | } 421 | 422 | .nav-item { 423 | font-size: 1.5rem !important; 424 | } 425 | 426 | .hide { 427 | animation: fadeOut 0.5s forwards; 428 | } 429 | 430 | .welcomeClass { 431 | background-color: #0f0f0f; 432 | } 433 | 434 | .welcomeTitle { 435 | display: flex; 436 | flex-direction: column; 437 | align-content: center; 438 | align-items: center; 439 | justify-content: center; 440 | margin-bottom: 4%; 441 | margin-top: -15%; 442 | } 443 | 444 | .welcomeContent { 445 | text-align-last: center; 446 | width: 60%; 447 | } 448 | 449 | .playerInformation { 450 | text-align: left; 451 | margin: 10px; 452 | } 453 | 454 | .noBreak { 455 | margin: 0; 456 | } 457 | 458 | .centerBox { 459 | display: flex; 460 | justify-content: center; 461 | flex-direction: column; 462 | align-items: center; 463 | height: 100%; 464 | } 465 | 466 | .show3 { 467 | -webkit-animation: 3s ease 0s normal forwards 1 fadein; 468 | animation: 3s ease 0s normal forwards 1 fadein; 469 | } 470 | 471 | .show4 { 472 | -webkit-animation: 3s ease 0.5s normal forwards 1 fadein; 473 | animation: 3s ease 0.5s normal forwards 1 fadein; 474 | } 475 | 476 | .show6 { 477 | -webkit-animation: 3s ease 1.5s normal forwards 1 fadein; 478 | animation: 3s ease 1.5s normal forwards 1 fadein; 479 | } 480 | 481 | @keyframes fadeOut { 482 | from { 483 | opacity: 1; 484 | } 485 | 486 | to { 487 | opacity: 0; 488 | display: none; 489 | } 490 | } 491 | 492 | @keyframes fadein{ 493 | 0% { opacity:0; } 494 | 5% { opacity:0; } 495 | 100% { opacity:1; } 496 | } 497 | 498 | @-webkit-keyframes fadein{ 499 | 0% { opacity:0; } 500 | 5% { opacity:0; } 501 | 100% { opacity:1; } 502 | } 503 | 504 | .vulxTitle { 505 | position: absolute; 506 | top: auto; 507 | left: auto; 508 | font-size: 125px; 509 | font-weight: 800; 510 | margin-top: -11rem; 511 | padding-right: 10px; 512 | } 513 | 514 | #loading { 515 | position: fixed; 516 | display: flex; 517 | justify-content: center; 518 | align-items: center; 519 | width: 100%; 520 | height: 100%; 521 | top: 0; 522 | left: 0; 523 | background-color: #0f0f0f; 524 | z-index: 99; 525 | } 526 | 527 | #loading-image { 528 | z-index: 100; 529 | } 530 | 531 | .searchBox { 532 | margin-right: 0px; 533 | margin-left: 0px; 534 | } 535 | 536 | .dropdownTitles { 537 | width: 100% !important; 538 | margin-top: 10px !important; 539 | height: 50px !important; 540 | border-radius: 10px !important; 541 | padding: 0.5rem 18px !important; 542 | border: 0px solid #d3d3d3 !important; 543 | text-align: left !important; 544 | background-color: #101116 !important; 545 | } 546 | 547 | .dropdownTitles:hover { 548 | color: #fff !important; 549 | border-bottom: 2px solid #d3d3d3 !important; 550 | } 551 | 552 | .searchBarInput { 553 | color: white; 554 | padding: 0.5rem 18px; 555 | border: 0px solid #d3d3d3; 556 | font-size: 16px; 557 | background-color: #101116; 558 | border-radius: 10px; 559 | -webkit-transition: border-bottom 0.1s ease; 560 | -moz-transition: border-bottom 0.1s ease; 561 | -ms-transition: border-bottom 0.1s ease; 562 | -o-transition: border-bottom 0.1s ease; 563 | transition: border-bottom 0.1s ease; 564 | width: 100%; 565 | height: 50px; 566 | margin-top: 10px; 567 | } 568 | 569 | .searchBarInput:hover { 570 | color: #fff; 571 | border-bottom: 2px solid #d3d3d3; 572 | } 573 | 574 | .searchBarInput:active { 575 | color: #fff; 576 | border-bottom: 2px solid #d3d3d3; 577 | } 578 | 579 | .selectBtn { 580 | display: inline-block; 581 | font-weight: 400; 582 | color: #fff; 583 | text-align: center; 584 | vertical-align: middle; 585 | -webkit-user-select: none; 586 | -moz-user-select: none; 587 | -ms-user-select: none; 588 | user-select: none; 589 | border: 1px solid transparent; 590 | padding: .375rem .9rem; 591 | font-size: 1rem; 592 | line-height: 1.5; 593 | } 594 | 595 | .themeSelect { 596 | color: #fff; 597 | margin-left: 10px; 598 | border-radius: 10px; 599 | border-bottom: 0px solid #d3d3d3; 600 | -webkit-transition: border-bottom 0.1s ease; 601 | -moz-transition: border-bottom 0.1s ease; 602 | -ms-transition: border-bottom 0.1s ease; 603 | -o-transition: border-bottom 0.1s ease; 604 | transition: border-bottom 0.1s ease; 605 | background-color: #101116; 606 | } 607 | 608 | .themeSelect:hover { 609 | color: #fff; 610 | border-bottom: 2px solid #d3d3d3; 611 | } 612 | 613 | .search-bar-cont { 614 | height: auto; 615 | width: 100%; 616 | position: absolute; 617 | border-radius: 10px; 618 | background-color: #101116; 619 | top: 70px; 620 | left: 0; 621 | z-index: 3; 622 | height: auto; 623 | max-height: 800px; 624 | overflow: scroll; 625 | } 626 | 627 | ::-webkit-scrollbar { 628 | display: none; 629 | } 630 | 631 | .search-bar-results { 632 | width: 100%; 633 | display: flex; 634 | margin: 30px; 635 | flex-wrap: wrap; 636 | } 637 | 638 | .search-bar-results-title { 639 | margin: 12px 0px -8px 40px; 640 | display: flex; 641 | } 642 | 643 | .search-bar-results-cards { 644 | display: flex; 645 | margin-bottom: 5px; 646 | margin-left: 30px; 647 | } 648 | 649 | .statusIcon { 650 | height: 19px; 651 | border-radius: 50%; 652 | position: absolute; 653 | border: 3px solid #101116; 654 | margin-left: 68px; 655 | margin-top: 68px; 656 | z-index: 1; 657 | } 658 | 659 | .search-bar-results-card { 660 | width: 370px; 661 | height: 100px; 662 | border-radius: 10px; 663 | -webkit-transition: transform .2s; 664 | -moz-transition: transform .2s; 665 | -ms-transition: transform .2s; 666 | -o-transition: transform .2s; 667 | transition: transform .2s; 668 | margin: 5px -14px 15px 10px; 669 | overflow: hidden; 670 | z-index: 0; 671 | } 672 | 673 | .search-bar-results-card:hover { 674 | transform: scale(1.05); 675 | z-index: 1; 676 | } 677 | 678 | .searchPfp { 679 | height: 80px; 680 | border-radius: 50%; 681 | position: absolute; 682 | border: 3px solid #101116; 683 | margin-left: 10px; 684 | margin-top: 10px; 685 | z-index: 1; 686 | } 687 | 688 | .searchBanner { 689 | height: 100px; 690 | width: 323px; 691 | border-radius: 20px; 692 | position: absolute; 693 | object-fit: cover; 694 | filter: brightness(0.5); 695 | } 696 | 697 | .searchInfo { 698 | position: relative; 699 | left: 98px; 700 | top: 25px; 701 | max-width: 55%; 702 | } 703 | 704 | .searchInfo2 { 705 | position: relative; 706 | left: 98px; 707 | top: 35px; 708 | max-width: 55%; 709 | } 710 | 711 | .textOverflow { 712 | white-space: nowrap; 713 | overflow: hidden; 714 | text-overflow: ellipsis; 715 | word-break: break-all; 716 | } 717 | 718 | .searchBarTags { 719 | display: flex; 720 | margin-top: 15px; 721 | } 722 | 723 | .themeName-large5 { 724 | font-weight: bold !important; 725 | font-size: 24px; 726 | vertical-align: middle; 727 | } 728 | 729 | .themeTagTextBar:hover { 730 | transform: scale(1.1); 731 | } 732 | 733 | .hidden { 734 | opacity: 0; 735 | display: none; 736 | } 737 | 738 | .visible { 739 | opacity: 1; 740 | display: block; 741 | } 742 | 743 | .fadein { 744 | -webkit-animation: fadein 0.3s ease-in; 745 | -moz-animation: fadein 0.3s ease-in; 746 | animation: fadein 0.3s ease-in; 747 | } 748 | 749 | .fadeout { 750 | -webkit-animation: fadeout 0.3s ease-out; 751 | -moz-animation: fadeout 0.3s ease-out; 752 | animation: fadeout 0.3s ease-out; 753 | } 754 | 755 | @keyframes fadein { 756 | from { opacity: 0; } 757 | to { opacity: 1; } 758 | } 759 | 760 | @keyframes fadeout { 761 | from { opacity: 1; } 762 | to { opacity: 0; } 763 | } -------------------------------------------------------------------------------- /public/experimental.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Vulx - Valorant 22 | 23 | 29 | 30 |
31 |
32 |

VULX

33 |
34 |

Loading Profile...

35 | Loading... 36 |
37 |
38 | 39 |

Vulx - Experimental

40 |

This area is still under development.
Check back after some updates and maybe this place will be populated!

41 | 44 | 47 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulxdev/Vulx/efec18f4e0d33ca588a5a445fbbba0f3cd35ba07/public/icon.ico -------------------------------------------------------------------------------- /public/images/Vulx-Puple-Blue.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulxdev/Vulx/efec18f4e0d33ca588a5a445fbbba0f3cd35ba07/public/images/Vulx-Puple-Blue.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Vulx - Valorant 22 | 23 | 24 |
25 |
26 |

VULX

27 |
28 |

29 | Loading... 30 |
31 | 34 |
35 |
36 | 37 |
38 |
39 | 68 |
69 | 78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 | 92 | 93 | 94 | 95 |
96 | 134 |
135 |

136 |
137 |
138 |
139 |
140 |
141 | 142 |

143 | 144 |
145 |
146 |
147 |
148 |
Vulx
149 |
150 |
151 |
152 |

153 |
🔹 :
154 |
🔹 :
155 |
🔹 :
156 |
157 |
158 |
159 |
Valorant • -
160 |
161 |
162 |
163 |
164 |
165 |
166 |
Aqua & Syfe
167 |
168 |
169 |
170 |
    171 |
  1. 172 |
    173 | 174 |
    175 |
    176 |
    177 | 178 |
    179 |
    180 |
    181 | 182 |
    183 |
    184 |
    185 | 186 |
    187 |
    188 |
    189 |
  2. 190 |
  3. 191 |
    192 | 193 |
    194 | 211 |
    212 |
    213 | 214 |
    215 |
    216 |
    217 | 218 | 219 | 220 |
    221 |
    222 |
  4. 223 |
  5. 224 |
    225 | 226 |
    227 |
    228 | 229 | 230 | 231 |
    232 |
    233 |
  6. 234 |
235 |
236 |
237 |
238 |
239 |
240 | 243 | 261 | 284 | 329 |
330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 349 | -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | let scripts = [ 2 | '/js/vulx.load.js', 3 | '/js/slip.js', 4 | '/js/vulx.welcome.js', 5 | '/js/vulx.profile.js', 6 | '/js/vulx.request.friends.js', 7 | '/js/vulx.request.reset.js', 8 | '/js/vulx.request.settings.js', 9 | '/js/vulx.request.session.js', 10 | '/js/vulx.search.js', 11 | '/js/vulx.localization.js', 12 | ]; 13 | 14 | let create = (info) => { 15 | return new Promise(function(resolve, reject) { 16 | let data = document.createElement('script'); 17 | data.type = "module"; 18 | data.src = info; 19 | data.async = false; 20 | data.onload = () => { 21 | resolve(info); 22 | }; 23 | data.onerror = () => { 24 | reject(info); 25 | }; 26 | document.body.appendChild(data); 27 | }); 28 | }; 29 | let promiseData = []; 30 | 31 | scripts.forEach(function(info) { 32 | promiseData.push(create(info)); 33 | console.log("[Vulx] Loading script: " + info) 34 | }); 35 | Promise.all(promiseData).then(function() { 36 | console.log('The required scripts are loaded successfully!'); 37 | }).catch(function(data) { 38 | console.log(data + ' failed to load!'); 39 | }); -------------------------------------------------------------------------------- /public/js/hierarchy.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";var h=function(e,t){this.$element=a(e),this.options=a.extend({},a.fn.hierarchySelect.defaults,t),this.$button=this.$element.children("button"),this.$menu=this.$element.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".hs-menu-inner"),this.$searchbox=this.$menu.find("input"),this.$hiddenField=this.$element.children("input"),this.previouslySelected=null,this.init()};h.prototype={constructor:h,init:function(){this.setWidth(),this.setHeight(),this.initSelect(),this.clickListener(),this.buttonListener(),this.searchListener()},initSelect:function(){var e=this.$menuInner.find("a[data-default-selected]:first");if(e.length)this.setValue(e.data("value"));else{var t=this.$menuInner.find("a:first");this.setValue(t.data("value"))}},setWidth:function(){if(this.$searchbox.attr("size",1),"auto"===this.options.width){var e=this.$menu.width();this.$element.css("min-width",e+2+"px")}else this.options.width?(this.$element.css("width",this.options.width),this.$menu.css("min-width",this.options.width),this.$button.css("width","100%")):this.$element.css("min-width","42px")},setHeight:function(){this.options.height&&(this.$menu.css("overflow","hidden"),this.$menuInner.css({"max-height":this.options.height,"overflow-y":"auto"}))},getText:function(){return this.$button.text()},getValue:function(){return this.$hiddenField.val()},setValue:function(e){var t=this.$menuInner.children('a[data-value="'+e+'"]:first');this.setSelected(t)},enable:function(){this.$button.removeAttr("disabled")},disable:function(){this.$button.attr("disabled","disabled")},setSelected:function(e){if(e.length&&this.previouslySelected!==e){var t=e.text(),n=e.data("value");this.previouslySelected=e,this.$button.html(t),this.$hiddenField.val(n),this.$menu.find(".active").removeClass("active"),e.addClass("active")}},moveUp:function(){var e=this.$menuInner.find("a:not(.d-none,.disabled)"),t=this.$menuInner.find(".active"),n=e.index(t);void 0!==e[n-1]&&(this.$menuInner.find(".active").removeClass("active"),e[n-1].classList.add("active"),s(this.$menuInner[0],e[n-1]))},moveDown:function(){var e=this.$menuInner.find("a:not(.d-none,.disabled)"),t=this.$menuInner.find(".active"),n=e.index(t);void 0!==e[n+1]&&(this.$menuInner.find(".active").removeClass("active"),e[n+1]&&(e[n+1].classList.add("active"),s(this.$menuInner[0],e[n+1])))},selectItem:function(){var e=this,t=this.$menuInner.find(".active");t.hasClass("d-none")||t.hasClass("disabled")||(setTimeout(function(){e.$button.focus()},5),t&&this.setSelected(t),this.$button.dropdown("toggle"))},clickListener:function(){var s=this;this.$element.on("show.bs.dropdown",function(){var n=s.$menuInner.find(".active");n&&setTimeout(function(){var e=n[0],t=n[0].parentNode;t.scrollTop<=e.offsetTop-t.offsetTop&&t.scrollTop+t.clientHeight>e.offsetTop+e.clientHeight||(e.parentNode.scrollTop=e.offsetTop-e.parentNode.offsetTop)},0)}),this.$element.on("hide.bs.dropdown",function(){s.previouslySelected&&s.setSelected(s.previouslySelected)}),this.$element.on("shown.bs.dropdown",function(){s.previouslySelected=s.$menuInner.find(".active"),s.$searchbox.focus()}),this.$menuInner.on("click","a",function(e){e.preventDefault();var t=a(this);t.hasClass("disabled")?e.stopPropagation():s.setSelected(t)})},buttonListener:function(){var t=this;this.options.search||this.$button.on("keydown",function(e){switch(e.keyCode){case 9:t.$element.hasClass("show")&&e.preventDefault();break;case 13:t.$element.hasClass("show")&&(e.preventDefault(),t.selectItem());break;case 27:t.$element.hasClass("show")&&(e.preventDefault(),e.stopPropagation(),t.$button.focus(),t.previouslySelected&&t.setSelected(t.previouslySelected),t.$button.dropdown("toggle"));break;case 38:t.$element.hasClass("show")&&(e.preventDefault(),e.stopPropagation(),t.moveUp());break;case 40:t.$element.hasClass("show")&&(e.preventDefault(),e.stopPropagation(),t.moveDown())}})},searchListener:function(){var s=this;this.options.search?(this.$searchbox.on("keydown",function(e){switch(e.keyCode){case 9:e.preventDefault(),e.stopPropagation(),s.$menuInner.click(),s.$button.focus();break;case 13:s.selectItem();break;case 27:e.preventDefault(),e.stopPropagation(),s.$button.focus(),s.previouslySelected&&s.setSelected(s.previouslySelected),s.$button.dropdown("toggle");break;case 38:e.preventDefault(),s.moveUp();break;case 40:e.preventDefault(),s.moveDown()}}),this.$searchbox.on("input propertychange",function(e){e.preventDefault();var t=s.$searchbox.val().toLowerCase(),n=s.$menuInner.find("a");0===t.length?n.each(function(){var e=a(this);e.toggleClass("disabled",!1),e.toggleClass("d-none",!1)}):n.each(function(){var e=a(this);-1!==e.text().toLowerCase().indexOf(t)?(e.toggleClass("disabled",!1),e.toggleClass("d-none",!1),s.options.hierarchy&&function(e){for(var t=e,n=t.data("level");"object"==typeof t&&0=t.offsetTop-e.offsetTop&&(e.scrollTop=t.offsetTop-e.offsetTop)}a.fn.hierarchySelect=function(s){var i,o=Array.prototype.slice.call(arguments,1),e=this.each(function(){var e=a(this),t=e.data("HierarchySelect"),n="object"==typeof s&&s;t||e.data("HierarchySelect",t=new h(this,n)),"string"==typeof s&&(i=t[s].apply(t,o))});return void 0===i?e:i},a.fn.hierarchySelect.defaults={width:"auto",height:"256px",hierarchy:!0,search:!0},a.fn.hierarchySelect.Constructor=h,a.fn.hierarchySelect.noConflict=function(){return a.fn.hierarchySelect=e,this}}(jQuery); -------------------------------------------------------------------------------- /public/js/slip.js: -------------------------------------------------------------------------------- 1 | /* 2 | Slip - swiping and reordering in lists of elements on touch screens, no fuss. 3 | 4 | Fires these events on list elements: 5 | 6 | • slip:swipe 7 | When swipe has been done and user has lifted finger off the screen. 8 | If you execute event.preventDefault() the element will be animated back to original position. 9 | Otherwise it will be animated off the list and set to display:none. 10 | 11 | • slip:beforeswipe 12 | Fired before first swipe movement starts. 13 | If you execute event.preventDefault() then element will not move at all. 14 | 15 | • slip:cancelswipe 16 | Fired after the user has started to swipe, but lets go without actually swiping left or right. 17 | 18 | • slip:animateswipe 19 | Fired while swiping, before the user has let go of the element. 20 | event.detail.x contains the amount of movement in the x direction. 21 | If you execute event.preventDefault() then the element will not move to this position. 22 | This can be useful for saturating the amount of swipe, or preventing movement in one direction, but allowing it in the other. 23 | 24 | • slip:reorder 25 | Element has been dropped in new location. event.detail contains the following: 26 | • insertBefore: DOM node before which element has been dropped (null is the end of the list). Use with node.insertBefore(). 27 | • spliceIndex: Index of element before which current element has been dropped, not counting the element iself. 28 | For use with Array.splice() if the list is reflecting objects in some array. 29 | • originalIndex: The original index of the element before it was reordered. 30 | 31 | • slip:beforereorder 32 | When reordering movement starts. 33 | Element being reordered gets class `slip-reordering`. 34 | If you execute event.preventDefault() then the element will not move at all. 35 | 36 | • slip:beforewait 37 | If you execute event.preventDefault() then reordering will begin immediately, blocking ability to scroll the page. 38 | 39 | • slip:tap 40 | When element was tapped without being swiped/reordered. You can check `event.target` to limit that behavior to drag handles. 41 | 42 | 43 | Usage: 44 | 45 | CSS: 46 | You should set `user-select:none` (and WebKit prefixes, sigh) on list elements, 47 | otherwise unstoppable and glitchy text selection in iOS will get in the way. 48 | 49 | You should set `overflow-x: hidden` on the container or body to prevent horizontal scrollbar 50 | appearing when elements are swiped off the list. 51 | 52 | 53 | var list = document.querySelector('ul#slippylist'); 54 | new Slip(list); 55 | 56 | list.addEventListener('slip:beforeswipe', function(e) { 57 | if (shouldNotSwipe(e.target)) e.preventDefault(); 58 | }); 59 | 60 | list.addEventListener('slip:swipe', function(e) { 61 | // e.target swiped 62 | if (thatWasSwipeToRemove) { 63 | e.target.parentNode.removeChild(e.target); 64 | } else { 65 | e.preventDefault(); // will animate back to original position 66 | } 67 | }); 68 | 69 | list.addEventListener('slip:beforereorder', function(e) { 70 | if (shouldNotReorder(e.target)) e.preventDefault(); 71 | }); 72 | 73 | list.addEventListener('slip:reorder', function(e) { 74 | // e.target reordered. 75 | if (reorderedOK) { 76 | e.target.parentNode.insertBefore(e.target, e.detail.insertBefore); 77 | } else { 78 | e.preventDefault(); 79 | } 80 | }); 81 | 82 | Requires: 83 | • Touch events 84 | • CSS transforms 85 | • Function.bind() 86 | 87 | Caveats: 88 | • Elements must not change size while reordering or swiping takes place (otherwise it will be visually out of sync) 89 | */ 90 | /*! @license 91 | Slip.js 1.2.0 92 | 93 | © 2014 Kornel Lesiński . All rights reserved. 94 | 95 | Redistribution and use in source and binary forms, with or without modification, 96 | are permitted provided that the following conditions are met: 97 | 98 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 99 | 100 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 101 | the following disclaimer in the documentation and/or other materials provided with the distribution. 102 | 103 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 104 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 105 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 106 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 107 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 108 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 109 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 110 | */ 111 | 112 | window['Slip'] = (function(){ 113 | 'use strict'; 114 | 115 | var accessibilityDefaults = { 116 | // Set values to false if you don't want Slip to manage them 117 | container: { 118 | role: "listbox", 119 | tabIndex: 0, 120 | focus: false, // focuses after drop 121 | }, 122 | items: { 123 | role: "option", // If "option" flattens items, try "group": 124 | // https://www.marcozehe.de/2013/03/08/sometimes-you-have-to-use-illegal-wai-aria-to-make-stuff-work/ 125 | tabIndex: -1, // 0 will make every item tabbable, which isn't always useful 126 | focus: false, // focuses when dragging 127 | }, 128 | }; 129 | 130 | var damnYouChrome = /Chrome\/[3-5]/.test(navigator.userAgent); // For bugs that can't be programmatically detected :( Intended to catch all versions of Chrome 30-40 131 | var needsBodyHandlerHack = damnYouChrome; // Otherwise I _sometimes_ don't get any touchstart events and only clicks instead. 132 | 133 | /* When dragging elements down in Chrome (tested 34-37) dragged element may appear below stationary elements. 134 | Looks like WebKit bug #61824, but iOS Safari doesn't have that problem. */ 135 | var compositorDoesNotOrderLayers = damnYouChrome; 136 | 137 | // -webkit-mess 138 | var testElementStyle = document.createElement('div').style; 139 | 140 | var transitionJSPropertyName = "transition" in testElementStyle ? "transition" : "webkitTransition"; 141 | var transformJSPropertyName = "transform" in testElementStyle ? "transform" : "webkitTransform"; 142 | var transformCSSPropertyName = transformJSPropertyName === "webkitTransform" ? "-webkit-transform" : "transform"; 143 | var userSelectJSPropertyName = "userSelect" in testElementStyle ? "userSelect" : "webkitUserSelect"; 144 | 145 | testElementStyle[transformJSPropertyName] = 'translateZ(0)'; 146 | var hwLayerMagicStyle = testElementStyle[transformJSPropertyName] ? 'translateZ(0) ' : ''; 147 | var hwTopLayerMagicStyle = testElementStyle[transformJSPropertyName] ? 'translateZ(1px) ' : ''; 148 | testElementStyle = null; 149 | 150 | var globalInstances = 0; 151 | var attachedBodyHandlerHack = false; 152 | var nullHandler = function(){}; 153 | 154 | function Slip(container, options) { 155 | if ('string' === typeof container) container = document.querySelector(container); 156 | if (!container || !container.addEventListener) throw new Error("Please specify DOM node to attach to"); 157 | 158 | if (!this || this === window) return new Slip(container, options); 159 | 160 | this.options = options = options || {}; 161 | this.options.keepSwipingPercent = options.keepSwipingPercent || 0; 162 | this.options.minimumSwipeVelocity = options.minimumSwipeVelocity || 1; 163 | this.options.minimumSwipeTime = options.minimumSwipeTime || 110; 164 | this.options.ignoredElements = options.ignoredElements || []; 165 | this.options.accessibility = options.accessibility || accessibilityDefaults; 166 | this.options.accessibility.container = options.accessibility.container || accessibilityDefaults.container; 167 | 168 | this.options.accessibility.container.role = options.accessibility.container.role !== undefined ? 169 | options.accessibility.container.role : 170 | accessibilityDefaults.container.role; 171 | 172 | this.options.accessibility.container.tabIndex = options.accessibility.container.tabIndex !== undefined ? 173 | options.accessibility.container.tabIndex : 174 | accessibilityDefaults.container.tabIndex; 175 | 176 | this.options.accessibility.container.focus = options.accessibility.container.focus !== undefined ? 177 | options.accessibility.container.focus : 178 | accessibilityDefaults.container.focus; 179 | 180 | this.options.accessibility.items = options.accessibility.items || accessibilityDefaults.items; 181 | 182 | this.options.accessibility.items.role = options.accessibility.items.role !== undefined ? 183 | options.accessibility.items.role : 184 | accessibilityDefaults.items.role; 185 | 186 | this.options.accessibility.items.tabIndex = options.accessibility.items.tabIndex !== undefined ? 187 | options.accessibility.items.tabIndex : 188 | accessibilityDefaults.items.tabIndex; 189 | 190 | this.options.accessibility.items.role = options.accessibility.items.focus !== undefined ? 191 | options.accessibility.items.focus : 192 | accessibilityDefaults.items.focus; 193 | 194 | if (!Array.isArray(this.options.ignoredElements)) throw new Error("ignoredElements must be an Array"); 195 | 196 | // Functions used for as event handlers need usable `this` and must not change to be removable 197 | this.cancel = this.setState.bind(this, this.states.idle); 198 | this.onTouchStart = this.onTouchStart.bind(this); 199 | this.onTouchMove = this.onTouchMove.bind(this); 200 | this.onTouchEnd = this.onTouchEnd.bind(this); 201 | this.onMouseDown = this.onMouseDown.bind(this); 202 | this.onMouseMove = this.onMouseMove.bind(this); 203 | this.onMouseUp = this.onMouseUp.bind(this); 204 | this.onMouseLeave = this.onMouseLeave.bind(this); 205 | this.onSelection = this.onSelection.bind(this); 206 | this.onContainerFocus = this.onContainerFocus.bind(this); 207 | 208 | this.setState(this.states.idle); 209 | this.attach(container); 210 | } 211 | 212 | function getTransform(node) { 213 | var transform = node.style[transformJSPropertyName]; 214 | if (transform) { 215 | return { 216 | value: transform, 217 | original: transform, 218 | }; 219 | } 220 | 221 | if (window.getComputedStyle) { 222 | var style = window.getComputedStyle(node).getPropertyValue(transformCSSPropertyName); 223 | if (style && style !== 'none') return {value:style, original:''}; 224 | } 225 | return {value:'', original:''}; 226 | } 227 | 228 | function findIndex(target, nodes) { 229 | var originalIndex = 0; 230 | var listCount = 0; 231 | 232 | for (var i=0; i < nodes.length; i++) { 233 | if (nodes[i].nodeType === 1) { 234 | listCount++; 235 | if (nodes[i] === target.node) { 236 | originalIndex = listCount-1; 237 | } 238 | } 239 | } 240 | 241 | return originalIndex; 242 | } 243 | 244 | // All functions in states are going to be executed in context of Slip object 245 | Slip.prototype = { 246 | 247 | container: null, 248 | options: {}, 249 | state: null, 250 | 251 | target: null, // the tapped/swiped/reordered node with height and backed up styles 252 | 253 | usingTouch: false, // there's no good way to detect touchscreen preference other than receiving a touch event (really, trust me). 254 | mouseHandlersAttached: false, 255 | 256 | startPosition: null, // x,y,time where first touch began 257 | latestPosition: null, // x,y,time where the finger is currently 258 | previousPosition: null, // x,y,time where the finger was ~100ms ago (for velocity calculation) 259 | 260 | canPreventScrolling: false, 261 | 262 | states: { 263 | idle: function idleStateInit() { 264 | this.removeMouseHandlers(); 265 | if (this.target) { 266 | this.target.node.style.willChange = ''; 267 | this.target = null; 268 | } 269 | this.usingTouch = false; 270 | 271 | return { 272 | allowTextSelection: true, 273 | }; 274 | }, 275 | 276 | undecided: function undecidedStateInit() { 277 | this.target.height = this.target.node.offsetHeight; 278 | this.target.node.style.willChange = transformCSSPropertyName; 279 | this.target.node.style[transitionJSPropertyName] = ''; 280 | 281 | if (!this.dispatch(this.target.originalTarget, 'beforewait')) { 282 | if (this.dispatch(this.target.originalTarget, 'beforereorder')) { 283 | this.setState(this.states.reorder); 284 | } 285 | } else { 286 | var holdTimer = setTimeout(function(){ 287 | var move = this.getAbsoluteMovement(); 288 | if (this.canPreventScrolling && move.x < 15 && move.y < 25) { 289 | if (this.dispatch(this.target.originalTarget, 'beforereorder')) { 290 | this.setState(this.states.reorder); 291 | } 292 | } 293 | }.bind(this), 300); 294 | } 295 | 296 | return { 297 | leaveState: function() { 298 | clearTimeout(holdTimer); 299 | }, 300 | 301 | onMove: function() { 302 | var move = this.getAbsoluteMovement(); 303 | 304 | if (move.x > 20 && move.y < Math.max(100, this.target.height)) { 305 | if (this.dispatch(this.target.originalTarget, 'beforeswipe', {directionX: move.directionX, directionY: move.directionY})) { 306 | this.setState(this.states.swipe); 307 | return false; 308 | } else { 309 | this.setState(this.states.idle); 310 | } 311 | } 312 | if (move.y > 20) { 313 | this.setState(this.states.idle); 314 | } 315 | 316 | // Chrome likes sideways scrolling :( 317 | if (move.x > move.y*1.2) return false; 318 | }, 319 | 320 | onLeave: function() { 321 | this.setState(this.states.idle); 322 | }, 323 | 324 | onEnd: function() { 325 | var allowDefault = this.dispatch(this.target.originalTarget, 'tap'); 326 | this.setState(this.states.idle); 327 | return allowDefault; 328 | }, 329 | }; 330 | }, 331 | 332 | swipe: function swipeStateInit() { 333 | var swipeSuccess = false; 334 | var container = this.container; 335 | 336 | var originalIndex = findIndex(this.target, this.container.childNodes); 337 | 338 | container.classList.add('slip-swiping-container'); 339 | function removeClass() { 340 | container.classList.remove('slip-swiping-container'); 341 | } 342 | 343 | this.target.height = this.target.node.offsetHeight; 344 | 345 | return { 346 | leaveState: function() { 347 | if (swipeSuccess) { 348 | this.animateSwipe(function(target){ 349 | target.node.style[transformJSPropertyName] = target.baseTransform.original; 350 | target.node.style[transitionJSPropertyName] = ''; 351 | if (this.dispatch(target.node, 'afterswipe')) { 352 | removeClass(); 353 | return true; 354 | } else { 355 | this.animateToZero(undefined, target); 356 | } 357 | }.bind(this)); 358 | } else { 359 | this.animateToZero(removeClass); 360 | } 361 | }, 362 | 363 | onMove: function() { 364 | var move = this.getTotalMovement(); 365 | 366 | if (Math.abs(move.y) < this.target.height+20) { 367 | if (this.dispatch(this.target.node, 'animateswipe', {x: move.x, originalIndex: originalIndex})) { 368 | this.target.node.style[transformJSPropertyName] = 'translate(' + move.x + 'px,0) ' + hwLayerMagicStyle + this.target.baseTransform.value; 369 | } 370 | return false; 371 | } else { 372 | this.dispatch(this.target.node, 'cancelswipe'); 373 | this.setState(this.states.idle); 374 | } 375 | }, 376 | 377 | onLeave: function() { 378 | this.state.onEnd.call(this); 379 | }, 380 | 381 | onEnd: function() { 382 | var move = this.getAbsoluteMovement(); 383 | var velocity = move.x / move.time; 384 | 385 | // How far out has the item been swiped? 386 | var swipedPercent = Math.abs((this.startPosition.x - this.previousPosition.x) / this.container.clientWidth) * 100; 387 | 388 | var swiped = (velocity > this.options.minimumSwipeVelocity && move.time > this.options.minimumSwipeTime) || (this.options.keepSwipingPercent && swipedPercent > this.options.keepSwipingPercent); 389 | 390 | if (swiped) { 391 | if (this.dispatch(this.target.node, 'swipe', {direction: move.directionX, originalIndex: originalIndex})) { 392 | swipeSuccess = true; // can't animate here, leaveState overrides anim 393 | } 394 | } else { 395 | this.dispatch(this.target.node, 'cancelswipe'); 396 | } 397 | this.setState(this.states.idle); 398 | return !swiped; 399 | }, 400 | }; 401 | }, 402 | 403 | reorder: function reorderStateInit() { 404 | if (this.target.node.focus && this.options.accessibility.items.focus) { 405 | this.target.node.focus(); 406 | } 407 | 408 | this.target.height = this.target.node.offsetHeight; 409 | 410 | var nodes; 411 | if (this.options.ignoredElements.length) { 412 | var container = this.container; 413 | var query = container.tagName.toLowerCase(); 414 | if (container.getAttribute('id')) { 415 | query = '#' + container.getAttribute('id'); 416 | } else if (container.classList.length) { 417 | query += '.' + container.getAttribute('class').replace(' ', '.'); 418 | } 419 | query += ' > '; 420 | this.options.ignoredElements.forEach(function (selector) { 421 | query += ':not(' + selector + ')'; 422 | }); 423 | try { 424 | nodes = container.parentNode.querySelectorAll(query); 425 | } catch(err) { 426 | if (err instanceof DOMException && err.name === 'SyntaxError') 427 | throw new Error('ignoredElements you specified contain invalid query'); 428 | else 429 | throw err; 430 | } 431 | } else { 432 | nodes = this.container.childNodes; 433 | } 434 | var originalIndex = findIndex(this.target, nodes); 435 | var mouseOutsideTimer; 436 | var zero = this.target.node.offsetTop + this.target.height/2; 437 | var otherNodes = []; 438 | for(var i=0; i < nodes.length; i++) { 439 | if (nodes[i].nodeType != 1 || nodes[i] === this.target.node) continue; 440 | var t = nodes[i].offsetTop; 441 | nodes[i].style[transitionJSPropertyName] = transformCSSPropertyName + ' 0.2s ease-in-out'; 442 | otherNodes.push({ 443 | node: nodes[i], 444 | baseTransform: getTransform(nodes[i]), 445 | pos: t + (t < zero ? nodes[i].offsetHeight : 0) - zero, 446 | }); 447 | } 448 | 449 | this.target.node.classList.add('slip-reordering'); 450 | this.target.node.style.zIndex = '99999'; 451 | this.target.node.style[userSelectJSPropertyName] = 'none'; 452 | if (compositorDoesNotOrderLayers) { 453 | // Chrome's compositor doesn't sort 2D layers 454 | this.container.style.webkitTransformStyle = 'preserve-3d'; 455 | } 456 | 457 | function onMove() { 458 | /*jshint validthis:true */ 459 | 460 | this.updateScrolling(); 461 | 462 | if (mouseOutsideTimer) { 463 | // don't care where the mouse is as long as it moves 464 | clearTimeout(mouseOutsideTimer); mouseOutsideTimer = null; 465 | } 466 | 467 | var move = this.getTotalMovement(); 468 | this.target.node.style[transformJSPropertyName] = 'translate(0,' + move.y + 'px) ' + hwTopLayerMagicStyle + this.target.baseTransform.value; 469 | 470 | var height = this.target.height; 471 | otherNodes.forEach(function(o){ 472 | var off = 0; 473 | if (o.pos < 0 && move.y < 0 && o.pos > move.y) { 474 | off = height; 475 | } 476 | else if (o.pos > 0 && move.y > 0 && o.pos < move.y) { 477 | off = -height; 478 | } 479 | // FIXME: should change accelerated/non-accelerated state lazily 480 | o.node.style[transformJSPropertyName] = off ? 'translate(0,'+off+'px) ' + hwLayerMagicStyle + o.baseTransform.value : o.baseTransform.original; 481 | }); 482 | return false; 483 | } 484 | 485 | onMove.call(this); 486 | 487 | return { 488 | leaveState: function() { 489 | if (mouseOutsideTimer) clearTimeout(mouseOutsideTimer); 490 | 491 | if (compositorDoesNotOrderLayers) { 492 | this.container.style.webkitTransformStyle = ''; 493 | } 494 | 495 | if (this.container.focus && this.options.accessibility.container.focus) { 496 | this.container.focus(); 497 | } 498 | 499 | this.target.node.classList.remove('slip-reordering'); 500 | this.target.node.style[userSelectJSPropertyName] = ''; 501 | 502 | this.animateToZero(function(target){ 503 | target.node.style.zIndex = ''; 504 | }); 505 | otherNodes.forEach(function(o){ 506 | o.node.style[transformJSPropertyName] = o.baseTransform.original; 507 | o.node.style[transitionJSPropertyName] = ''; // FIXME: animate to new position 508 | }); 509 | }, 510 | 511 | onMove: onMove, 512 | 513 | onLeave: function() { 514 | // don't let element get stuck if mouse left the window 515 | // but don't cancel immediately as it'd be annoying near window edges 516 | if (mouseOutsideTimer) clearTimeout(mouseOutsideTimer); 517 | mouseOutsideTimer = setTimeout(function(){ 518 | mouseOutsideTimer = null; 519 | this.cancel(); 520 | }.bind(this), 700); 521 | }, 522 | 523 | onEnd: function() { 524 | var move = this.getTotalMovement(); 525 | var i, spliceIndex; 526 | if (move.y < 0) { 527 | for (i=0; i < otherNodes.length; i++) { 528 | if (otherNodes[i].pos > move.y) { 529 | break; 530 | } 531 | } 532 | spliceIndex = i; 533 | } else { 534 | for (i=otherNodes.length-1; i >= 0; i--) { 535 | if (otherNodes[i].pos < move.y) { 536 | break; 537 | } 538 | } 539 | spliceIndex = i+1; 540 | } 541 | 542 | this.dispatch(this.target.node, 'reorder', { 543 | spliceIndex: spliceIndex, 544 | originalIndex: originalIndex, 545 | insertBefore: otherNodes[spliceIndex] ? otherNodes[spliceIndex].node : null, 546 | }); 547 | 548 | this.setState(this.states.idle); 549 | return false; 550 | }, 551 | }; 552 | }, 553 | }, 554 | 555 | attach: function(container) { 556 | globalInstances++; 557 | if (this.container) this.detach(); 558 | 559 | // In some cases taps on list elements send *only* click events and no touch events. Spotted only in Chrome 32+ 560 | // Having event listener on body seems to solve the issue (although AFAIK may disable smooth scrolling as a side-effect) 561 | if (!attachedBodyHandlerHack && needsBodyHandlerHack) { 562 | attachedBodyHandlerHack = true; 563 | document.body.addEventListener('touchstart', nullHandler, false); 564 | } 565 | 566 | this.container = container; 567 | 568 | // Accessibility 569 | if (false !== this.options.accessibility.container.tabIndex) { 570 | this.container.tabIndex = this.options.accessibility.container.tabIndex; 571 | } 572 | if (this.options.accessibility.container.role) { 573 | this.container.setAttribute('role', this.options.accessibility.container.role); 574 | } 575 | this.setChildNodesRoles(); 576 | this.container.addEventListener('focus', this.onContainerFocus, false); 577 | 578 | this.otherNodes = []; 579 | 580 | // selection on iOS interferes with reordering 581 | document.addEventListener("selectionchange", this.onSelection, false); 582 | 583 | // cancel is called e.g. when iOS detects multitasking gesture 584 | this.container.addEventListener('touchcancel', this.cancel, false); 585 | this.container.addEventListener('touchstart', this.onTouchStart, false); 586 | this.container.addEventListener('touchmove', this.onTouchMove, false); 587 | this.container.addEventListener('touchend', this.onTouchEnd, false); 588 | this.container.addEventListener('mousedown', this.onMouseDown, false); 589 | // mousemove and mouseup are attached dynamically 590 | }, 591 | 592 | detach: function() { 593 | this.cancel(); 594 | 595 | this.container.removeEventListener('mousedown', this.onMouseDown, false); 596 | this.container.removeEventListener('touchend', this.onTouchEnd, false); 597 | this.container.removeEventListener('touchmove', this.onTouchMove, false); 598 | this.container.removeEventListener('touchstart', this.onTouchStart, false); 599 | this.container.removeEventListener('touchcancel', this.cancel, false); 600 | 601 | document.removeEventListener("selectionchange", this.onSelection, false); 602 | 603 | if (false !== this.options.accessibility.container.tabIndex) { 604 | this.container.removeAttribute('tabIndex'); 605 | } 606 | if (this.options.accessibility.container.role) { 607 | this.container.removeAttribute('role'); 608 | } 609 | this.unSetChildNodesRoles(); 610 | 611 | globalInstances--; 612 | if (!globalInstances && attachedBodyHandlerHack) { 613 | attachedBodyHandlerHack = false; 614 | document.body.removeEventListener('touchstart', nullHandler, false); 615 | } 616 | }, 617 | 618 | setState: function(newStateCtor){ 619 | if (this.state) { 620 | if (this.state.ctor === newStateCtor) return; 621 | if (this.state.leaveState) this.state.leaveState.call(this); 622 | } 623 | 624 | // Must be re-entrant in case ctor changes state 625 | var prevState = this.state; 626 | var nextState = newStateCtor.call(this); 627 | if (this.state === prevState) { 628 | nextState.ctor = newStateCtor; 629 | this.state = nextState; 630 | } 631 | }, 632 | 633 | findTargetNode: function(targetNode) { 634 | while(targetNode && targetNode.parentNode !== this.container) { 635 | targetNode = targetNode.parentNode; 636 | } 637 | return targetNode; 638 | }, 639 | 640 | onContainerFocus: function(e) { 641 | e.stopPropagation(); 642 | this.setChildNodesRoles(); 643 | }, 644 | 645 | setChildNodesRoles: function() { 646 | var nodes = this.container.childNodes; 647 | for(var i=0; i < nodes.length; i++) { 648 | if (nodes[i].nodeType != 1) continue; 649 | if (this.options.accessibility.items.role) { 650 | nodes[i].setAttribute('role', this.options.accessibility.items.role); 651 | } 652 | if (false !== this.options.accessibility.items.tabIndex) { 653 | nodes[i].tabIndex = this.options.accessibility.items.tabIndex; 654 | } 655 | } 656 | }, 657 | 658 | unSetChildNodesRoles: function() { 659 | var nodes = this.container.childNodes; 660 | for(var i=0; i < nodes.length; i++) { 661 | if (nodes[i].nodeType != 1) continue; 662 | if (this.options.accessibility.items.role) { 663 | nodes[i].removeAttribute('role'); 664 | } 665 | if (false !== this.options.accessibility.items.tabIndex) { 666 | nodes[i].removeAttribute('tabIndex'); 667 | } 668 | } 669 | }, 670 | onSelection: function(e) { 671 | e.stopPropagation(); 672 | var isRelated = e.target === document || this.findTargetNode(e); 673 | var iOS = /(iPhone|iPad|iPod)/i.test(navigator.userAgent) && !/(Android|Windows)/i.test(navigator.userAgent); 674 | if (!isRelated) return; 675 | 676 | if (iOS) { 677 | // iOS doesn't allow selection to be prevented 678 | this.setState(this.states.idle); 679 | } else { 680 | if (!this.state.allowTextSelection) { 681 | e.preventDefault(); 682 | } 683 | } 684 | }, 685 | 686 | addMouseHandlers: function() { 687 | // unlike touch events, mousemove/up is not conveniently fired on the same element, 688 | // but I don't need to listen to unrelated events all the time 689 | if (!this.mouseHandlersAttached) { 690 | this.mouseHandlersAttached = true; 691 | document.documentElement.addEventListener('mouseleave', this.onMouseLeave, false); 692 | window.addEventListener('mousemove', this.onMouseMove, true); 693 | window.addEventListener('mouseup', this.onMouseUp, true); 694 | window.addEventListener('blur', this.cancel, false); 695 | } 696 | }, 697 | 698 | removeMouseHandlers: function() { 699 | if (this.mouseHandlersAttached) { 700 | this.mouseHandlersAttached = false; 701 | document.documentElement.removeEventListener('mouseleave', this.onMouseLeave, false); 702 | window.removeEventListener('mousemove', this.onMouseMove, true); 703 | window.removeEventListener('mouseup', this.onMouseUp, true); 704 | window.removeEventListener('blur', this.cancel, false); 705 | } 706 | }, 707 | 708 | onMouseLeave: function(e) { 709 | e.stopPropagation(); 710 | if (this.usingTouch) return; 711 | 712 | if (e.target === document.documentElement || e.relatedTarget === document.documentElement) { 713 | if (this.state.onLeave) { 714 | this.state.onLeave.call(this); 715 | } 716 | } 717 | }, 718 | 719 | onMouseDown: function(e) { 720 | e.stopPropagation(); 721 | if (this.usingTouch || e.button != 0 || !this.setTarget(e)) return; 722 | 723 | this.addMouseHandlers(); // mouseup, etc. 724 | 725 | this.canPreventScrolling = true; // or rather it doesn't apply to mouse 726 | 727 | this.startAtPosition({ 728 | x: e.clientX, 729 | y: e.clientY, 730 | time: e.timeStamp, 731 | }); 732 | }, 733 | 734 | onTouchStart: function(e) { 735 | e.stopPropagation(); 736 | this.usingTouch = true; 737 | this.canPreventScrolling = true; 738 | 739 | // This implementation cares only about single touch 740 | if (e.touches.length > 1) { 741 | this.setState(this.states.idle); 742 | return; 743 | } 744 | 745 | if (!this.setTarget(e)) return; 746 | 747 | this.startAtPosition({ 748 | x: e.touches[0].clientX, 749 | y: e.touches[0].clientY, 750 | time: e.timeStamp, 751 | }); 752 | }, 753 | 754 | setTarget: function(e) { 755 | var targetNode = this.findTargetNode(e.target); 756 | if (!targetNode) { 757 | this.setState(this.states.idle); 758 | return false; 759 | } 760 | 761 | // scrollContainer may be explicitly set via options, otherwise search upwards for a parent with an overflow-y property 762 | // fallback to document.scrollingElement (or documentElement on IE), and do not use document.body 763 | var scrollContainer = this.options.scrollContainer; 764 | if (!scrollContainer) { 765 | var top = document.scrollingElement || document.documentElement; 766 | scrollContainer = targetNode.parentNode; 767 | while (scrollContainer) { 768 | if (scrollContainer == top) break; 769 | if (scrollContainer != document.body && scrollContainer.scrollHeight > scrollContainer.clientHeight && window.getComputedStyle(scrollContainer)['overflow-y'] != 'visible') break; 770 | scrollContainer = scrollContainer.parentNode; 771 | } 772 | scrollContainer = scrollContainer || top; 773 | } 774 | 775 | this.target = { 776 | originalTarget: e.target, 777 | node: targetNode, 778 | scrollContainer: scrollContainer, 779 | origScrollTop: scrollContainer.scrollTop, 780 | origScrollHeight: scrollContainer.scrollHeight, 781 | baseTransform: getTransform(targetNode), 782 | }; 783 | return true; 784 | }, 785 | 786 | startAtPosition: function(pos) { 787 | this.startPosition = this.previousPosition = this.latestPosition = pos; 788 | this.setState(this.states.undecided); 789 | }, 790 | 791 | updatePosition: function(e, pos) { 792 | if (this.target == null) { 793 | return; 794 | } 795 | this.latestPosition = pos; 796 | 797 | if (this.state.onMove) { 798 | if (this.state.onMove.call(this) === false) { 799 | e.preventDefault(); 800 | } 801 | } 802 | 803 | // sample latestPosition 100ms for velocity 804 | if (this.latestPosition.time - this.previousPosition.time > 100) { 805 | this.previousPosition = this.latestPosition; 806 | } 807 | }, 808 | 809 | onMouseMove: function(e) { 810 | e.stopPropagation(); 811 | this.updatePosition(e, { 812 | x: e.clientX, 813 | y: e.clientY, 814 | time: e.timeStamp, 815 | }); 816 | }, 817 | 818 | onTouchMove: function(e) { 819 | e.stopPropagation(); 820 | this.updatePosition(e, { 821 | x: e.touches[0].clientX, 822 | y: e.touches[0].clientY, 823 | time: e.timeStamp, 824 | }); 825 | 826 | // In Apple's touch model only the first move event after touchstart can prevent scrolling (and event.cancelable is broken) 827 | this.canPreventScrolling = false; 828 | }, 829 | 830 | onMouseUp: function(e) { 831 | e.stopPropagation(); 832 | if (this.usingTouch || e.button !== 0) return; 833 | 834 | if (this.state.onEnd && false === this.state.onEnd.call(this)) { 835 | e.preventDefault(); 836 | } 837 | }, 838 | 839 | onTouchEnd: function(e) { 840 | e.stopPropagation(); 841 | if (e.touches.length > 1) { 842 | this.cancel(); 843 | } else if (this.state.onEnd && false === this.state.onEnd.call(this)) { 844 | e.preventDefault(); 845 | } 846 | }, 847 | 848 | getTotalMovement: function() { 849 | var scrollOffset = this.target.scrollContainer.scrollTop - this.target.origScrollTop; 850 | return { 851 | x: this.latestPosition.x - this.startPosition.x, 852 | y: this.latestPosition.y - this.startPosition.y + scrollOffset, 853 | time: this.latestPosition.time - this.startPosition.time, 854 | }; 855 | }, 856 | 857 | getAbsoluteMovement: function() { 858 | var move = this.getTotalMovement(); 859 | return { 860 | x: Math.abs(move.x), 861 | y: Math.abs(move.y), 862 | time: move.time, 863 | directionX: move.x < 0 ? 'left' : 'right', 864 | directionY: move.y < 0 ? 'up' : 'down', 865 | }; 866 | }, 867 | 868 | updateScrolling: function() { 869 | var triggerOffset = 40, 870 | offset = 0; 871 | 872 | var scrollable = this.target.scrollContainer, 873 | containerRect = scrollable.getBoundingClientRect(), 874 | targetRect = this.target.node.getBoundingClientRect(), 875 | bottomOffset = Math.min(containerRect.bottom, window.innerHeight) - targetRect.bottom, 876 | topOffset = targetRect.top - Math.max(containerRect.top, 0), 877 | maxScrollTop = this.target.origScrollHeight - Math.min(scrollable.clientHeight, window.innerHeight); 878 | 879 | if (bottomOffset < triggerOffset) { 880 | offset = Math.min(triggerOffset, triggerOffset - bottomOffset); 881 | } 882 | else if (topOffset < triggerOffset) { 883 | offset = Math.max(-triggerOffset, topOffset - triggerOffset); 884 | } 885 | 886 | scrollable.scrollTop = Math.max(0, Math.min(maxScrollTop, scrollable.scrollTop + offset)); 887 | }, 888 | 889 | dispatch: function(targetNode, eventName, detail) { 890 | var event = document.createEvent('CustomEvent'); 891 | if (event && event.initCustomEvent) { 892 | event.initCustomEvent('slip:' + eventName, true, true, detail); 893 | } else { 894 | event = document.createEvent('Event'); 895 | event.initEvent('slip:' + eventName, true, true); 896 | event.detail = detail; 897 | } 898 | return targetNode.dispatchEvent(event); 899 | }, 900 | 901 | getSiblings: function(target) { 902 | var siblings = []; 903 | var tmp = target.node.nextSibling; 904 | while(tmp) { 905 | if (tmp.nodeType == 1) siblings.push({ 906 | node: tmp, 907 | baseTransform: getTransform(tmp), 908 | }); 909 | tmp = tmp.nextSibling; 910 | } 911 | return siblings; 912 | }, 913 | 914 | animateToZero: function(callback, target) { 915 | // save, because this.target/container could change during animation 916 | target = target || this.target; 917 | 918 | target.node.style[transitionJSPropertyName] = transformCSSPropertyName + ' 0.1s ease-out'; 919 | target.node.style[transformJSPropertyName] = 'translate(0,0) ' + hwLayerMagicStyle + target.baseTransform.value; 920 | setTimeout(function(){ 921 | target.node.style[transitionJSPropertyName] = ''; 922 | target.node.style[transformJSPropertyName] = target.baseTransform.original; 923 | if (callback) callback.call(this, target); 924 | }.bind(this), 101); 925 | }, 926 | 927 | animateSwipe: function(callback) { 928 | var target = this.target; 929 | var siblings = this.getSiblings(target); 930 | var emptySpaceTransformStyle = 'translate(0,' + this.target.height + 'px) ' + hwLayerMagicStyle + ' '; 931 | 932 | // FIXME: animate with real velocity 933 | target.node.style[transitionJSPropertyName] = 'all 0.1s linear'; 934 | target.node.style[transformJSPropertyName] = ' translate(' + (this.getTotalMovement().x > 0 ? '' : '-') + '100%,0) ' + hwLayerMagicStyle + target.baseTransform.value; 935 | 936 | setTimeout(function(){ 937 | if (callback.call(this, target)) { 938 | siblings.forEach(function(o){ 939 | o.node.style[transitionJSPropertyName] = ''; 940 | o.node.style[transformJSPropertyName] = emptySpaceTransformStyle + o.baseTransform.value; 941 | }); 942 | setTimeout(function(){ 943 | siblings.forEach(function(o){ 944 | o.node.style[transitionJSPropertyName] = transformCSSPropertyName + ' 0.1s ease-in-out'; 945 | o.node.style[transformJSPropertyName] = 'translate(0,0) ' + hwLayerMagicStyle + o.baseTransform.value; 946 | }); 947 | setTimeout(function(){ 948 | siblings.forEach(function(o){ 949 | o.node.style[transitionJSPropertyName] = ''; 950 | o.node.style[transformJSPropertyName] = o.baseTransform.original; 951 | }); 952 | }, 101); 953 | }, 1); 954 | } 955 | }.bind(this), 101); 956 | }, 957 | }; 958 | 959 | // AMD 960 | if ('function' === typeof define && define.amd) { 961 | define(function(){ 962 | return Slip; 963 | }); 964 | } 965 | // CJS 966 | if ('object' === typeof module && module.exports) { 967 | module.exports = Slip; 968 | } 969 | return Slip; 970 | })(); 971 | -------------------------------------------------------------------------------- /public/js/vulx.load.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // Loading screen for Vulx, this is the first thing that will be loaded (may remove this later) 9 | 10 | function delay(time) { 11 | return new Promise(function (resolve) { 12 | setTimeout(resolve, time); 13 | }); 14 | } 15 | 16 | $(window).on('load', async function () { 17 | await delay(500); 18 | document.getElementById("loading").classList.add("hide"); 19 | $('#loading').bind('animationend', function() { $(this).remove(); }); 20 | }) -------------------------------------------------------------------------------- /public/js/vulx.localization.js: -------------------------------------------------------------------------------- 1 | const loadPath = `/locales/{{lng}}/{{ns}}.json`; 2 | 3 | $(function () { 4 | i18next 5 | .use(i18nextBrowserLanguageDetector) 6 | .use(i18nextHttpBackend) 7 | .init({ 8 | debug: true, 9 | fallbackLng: 'en', 10 | ns: ["default"], 11 | defaultNS: "default", 12 | backend: { 13 | loadPath: loadPath 14 | } 15 | }, (err, t) => { 16 | jqueryI18next.init(i18next, $, { useOptionsAttr: true }); 17 | $('body').localize(); 18 | }); 19 | }); -------------------------------------------------------------------------------- /public/js/vulx.profile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const searchBar = document.querySelector('input[type="text"]'); 9 | import ranksJson from '../json/ranks.json' assert {type: 'json'}; 10 | 11 | window.autosaveUrl = "http://127.0.0.1:/updatePresence"; 12 | 13 | function delay(time) { 14 | return new Promise(function (resolve) { 15 | setTimeout(resolve, time); 16 | }); 17 | } 18 | 19 | if(searchBar.addEventListener('focusin', (event) => { 20 | var overlay = document.getElementById("overlay"); 21 | var searchBar = document.getElementById("searchBar"); 22 | //Remove old class 23 | overlay.classList.remove("hidden"); 24 | overlay.classList.remove("fadeout"); 25 | searchBar.classList.remove("hidden"); 26 | searchBar.classList.remove("fadeout"); 27 | //Add new class 28 | overlay.classList.add("visible"); 29 | overlay.classList.add("fadein"); 30 | searchBar.classList.add("visible"); 31 | searchBar.classList.add("fadein"); 32 | })); 33 | 34 | function closeSearchBar() { 35 | var overlay = document.getElementById("overlay"); 36 | var searchBar = document.getElementById("searchBar"); 37 | //Remove old class 38 | overlay.classList.remove("visible"); 39 | overlay.classList.remove("fadein"); 40 | searchBar.classList.remove("visible"); 41 | searchBar.classList.remove("fadein"); 42 | //Add new class 43 | overlay.classList.add("hidden"); 44 | overlay.classList.add("fadeout"); 45 | searchBar.classList.add("hidden"); 46 | searchBar.classList.add("fadeout"); 47 | 48 | } window.closeSearchBar = closeSearchBar; 49 | 50 | function Notification(type, message) { 51 | if(type == true) { 52 | Toastify({ text: message, 53 | duration: 3000, 54 | close: true, 55 | gravity: "bottom", 56 | position: "right", 57 | stopOnFocus: true, 58 | className: "info", 59 | }).showToast(); 60 | } else { 61 | Toastify({ text: message, 62 | duration: 3000, 63 | close: true, 64 | gravity: "bottom", 65 | position: "right", 66 | stopOnFocus: true, 67 | className: "info", 68 | style: { 69 | background: "linear-gradient(to right, #ff5f6d, #ffc371)", 70 | } 71 | }).showToast(); 72 | } 73 | } 74 | 75 | async function selectRank(id) { 76 | await fetch('http://127.0.0.1:/updatePresence', { 77 | method: 'POST', 78 | headers: { 79 | 'Accept': 'application/json', 80 | 'Content-Type': 'application/json' 81 | }, 82 | body: JSON.stringify( 83 | { 84 | flag: 2, 85 | rank: id 86 | } 87 | ) 88 | }).then((response) => { 89 | Notification(true, "Rank updated successfully!"); 90 | getProfile(); 91 | }).catch((error) => { 92 | Notification(false, "An error occured while updating your rank!"); 93 | }); 94 | } window.selectRank = selectRank; 95 | 96 | function selectStatus(id) { 97 | fetch('http://127.0.0.1:/updateStatus', { 98 | method: 'POST', 99 | headers: { 100 | 'Accept': 'application/json', 101 | 'Content-Type': 'application/json' 102 | }, 103 | body: JSON.stringify( 104 | { 105 | status: id 106 | } 107 | ) 108 | }).then((response) => { 109 | Notification(true, "Status updated successfully!"); 110 | getProfile(); 111 | }).catch((error) => { 112 | Notification(false, "An error occured while updating your Status!"); 113 | }); 114 | } window.selectStatus = selectStatus; 115 | 116 | function rankDropdownToggle(el) { 117 | var rankDropdowns = document.querySelectorAll(".arrow-downV2"); 118 | rankDropdowns.forEach(rankDropdown => { 119 | if (rankDropdown.id != "mainRankDropdown" && rankDropdown != el) 120 | rankDropdown.classList.remove("active") 121 | }) 122 | el.classList.toggle("active") 123 | } window.rankDropdownToggle = rankDropdownToggle; 124 | 125 | function selectTitle(playerTitleId) { 126 | fetch(window.autosaveUrl, { 127 | method: 'POST', 128 | headers: { 129 | 'Accept': 'application/json', 130 | 'Content-Type': 'application/json' 131 | }, 132 | body: JSON.stringify( 133 | { 134 | flag: 64, 135 | playerTitleId, 136 | } 137 | ) 138 | }).then(() => { 139 | Notification(true, "Profile updated successfully!"); 140 | getProfile(); 141 | }).catch(() => { 142 | Notification(false, "An error occured while updating your profile!"); 143 | }) 144 | } 145 | 146 | let dropLoc = 0; 147 | for(var i = 0; i < ranksJson.length; i++) { 148 | var ranksDropdown = document.getElementById("collapseRank"); 149 | var rank = document.createElement("div"); 150 | rank.setAttribute("class", "valorantDropdownItem"); 151 | rank.setAttribute("data-toggle", "collapse"); 152 | rank.setAttribute("href", "#collapseRankSpecific" + i); 153 | rank.setAttribute("role", "button"); 154 | rank.setAttribute("aria-expanded", "false"); 155 | rank.setAttribute("aria-controls", "collapseRankSpecific" + i); 156 | rank.setAttribute("onclick", "rankDropdownToggle(this.children[2])"); 157 | rank.setAttribute("id", i); 158 | 159 | var rankImg = document.createElement("img"); 160 | rankImg.setAttribute("style", "height: 30px;"); 161 | rankImg.setAttribute("class", "valorantRankImg"); 162 | rankImg.setAttribute("src", `https://cdn.aquaplays.xyz/ranks/${ranksJson[i].id <= 2 ? 0 : ranksJson[i].id}.png`); 163 | 164 | const rankNameText = Object.keys(ranksJson[i])[1]; 165 | 166 | const rankName = document.createElement("h4"); 167 | rankName.setAttribute("style", "font-size: 20px; padding-top: 2px;"); 168 | rankName.setAttribute("class", "valorantRank"); 169 | rankName.setAttribute("data-i18n", `ranks.${rankNameText.toLowerCase().replace(' ', '')}`); 170 | rank.appendChild(rankImg); 171 | 172 | rank.appendChild(rankName); 173 | 174 | var rankArrow = document.createElement("div"); 175 | rankArrow.setAttribute("style", `top: ${dropLoc}px !important;`); 176 | rankArrow.setAttribute("class", "arrow-left arrow-downV2"); 177 | rankArrow.setAttribute("id", "rankArrow" + i); 178 | rank.appendChild(rankArrow); 179 | 180 | var rankSpecificDropdown = document.createElement("div"); 181 | rankSpecificDropdown.setAttribute("data-parent", "#collapseRank"); 182 | rankSpecificDropdown.setAttribute("style", `top: ${dropLoc}px !important;`); 183 | rankSpecificDropdown.setAttribute("id", "collapseRankSpecific" + i); 184 | rankSpecificDropdown.setAttribute("class", "collapse profileRankSpecificDropdown"); 185 | rank.appendChild(rankSpecificDropdown); 186 | 187 | for(var j = 0; j < Object.values(ranksJson[i])[1].length; j++) { 188 | let rank = Object.values(ranksJson[i])[1][j]; 189 | 190 | if(rankNameText == "No Rank") { 191 | //add tooltips for no rank display 192 | var rankSpecificTooltip = document.createElement("a"); 193 | rankSpecificTooltip.setAttribute("data-toggle", "tooltip"); 194 | rankSpecificTooltip.setAttribute("data-placement", "right"); 195 | rankSpecificTooltip.setAttribute("data-i18n", "[title]tooltip.noRank"); 196 | rankSpecificTooltip.setAttribute("class", "customTooltip"); 197 | rankSpecificDropdown.appendChild(rankSpecificTooltip); 198 | } 199 | 200 | if(rankNameText == "Special") { 201 | //add tooltips to special ranks 202 | var rankSpecificTooltip = document.createElement("a"); 203 | rankSpecificTooltip.setAttribute("data-toggle", "tooltip"); 204 | rankSpecificTooltip.setAttribute("data-placement", "right"); 205 | rankSpecificTooltip.setAttribute("data-i18n", "[title]tooltip.specialRank"); 206 | rankSpecificTooltip.setAttribute("class", "customTooltip"); 207 | rankSpecificDropdown.appendChild(rankSpecificTooltip); 208 | } 209 | 210 | var rankSpecific = document.createElement("div"); 211 | rankSpecific.setAttribute("class", "valorantDropdownItem"); 212 | rankSpecific.setAttribute("id", j); 213 | rankSpecific.addEventListener('click', async (event) => { 214 | await selectRank(rank); 215 | }); 216 | 217 | if(i <= 1) { 218 | rankSpecificTooltip.appendChild(rankSpecific); 219 | } else { 220 | rankSpecificDropdown.appendChild(rankSpecific); 221 | } 222 | 223 | var rankSpecificImg = document.createElement("img"); 224 | rankSpecificImg.setAttribute("style", "height: 30px;"); 225 | rankSpecificImg.setAttribute("class", "valorantRankImg"); 226 | rankSpecificImg.setAttribute("src", `https://cdn.aquaplays.xyz/ranks/${i <= 2 ? 0 : rank}.png`); 227 | if(!(i <= 2)) { 228 | rankSpecific.appendChild(rankSpecificImg); 229 | } 230 | 231 | const num = j+1; 232 | var rankSpecificName = document.createElement("h4"); 233 | rankSpecificName.setAttribute("style", "font-size: 20px; padding-top: 2px;"); 234 | rankSpecificName.setAttribute("class", "valorantRank"); 235 | 236 | const rankNameSpan = document.createElement("span"); 237 | rankNameSpan.setAttribute("data-i18n", `ranks.${rankNameText.toLowerCase().replace(' ', '')}`); 238 | rankSpecificName.appendChild(rankNameSpan); 239 | 240 | if (i >= 3 && i <= 10 || i == 1) { 241 | var rankNumSpan = document.createElement("span"); 242 | rankNumSpan.innerText = ` ${num}`; 243 | rankSpecificName.appendChild(rankNumSpan); 244 | } 245 | 246 | rankSpecific.appendChild(rankSpecificName); 247 | } 248 | 249 | dropLoc += 45; 250 | ranksDropdown.appendChild(rank); 251 | } 252 | 253 | if ($('#valorantMatchStatus')[0].scrollWidth > $('#valorantMatchStatusContainer').innerWidth()) { 254 | const isHover = e => e.parentElement.querySelector(':hover') === e; 255 | const valorantStatus = document.getElementById('valorantMatchStatus'); 256 | document.addEventListener('mousemove', function checkHover() { 257 | const hovered = isHover(valorantStatus); 258 | if (hovered !== checkHover.hovered) { 259 | if(hovered == true) { 260 | var background = getComputedStyle(document.getElementById('valorantMatchStatusContainer')).getPropertyValue('height'); 261 | document.getElementById("profile").style.height = parseInt(background) + 505 - 37 + "px"; 262 | document.getElementById("bottomSpacer").style.top = parseInt(background) + 452 - 37 + "px"; 263 | document.getElementById("valorantMatchStatus").style.whiteSpace = "normal";; 264 | document.getElementsByClassName("vulxAdvertising")[0].style.top = parseInt(background) + 475 - 37 + "px"; 265 | } else { 266 | document.getElementById("profile").style.height = "505px"; 267 | document.getElementById("bottomSpacer").style.top = "452px"; 268 | document.getElementById("valorantMatchStatus").style.whiteSpace = "nowrap"; 269 | document.getElementsByClassName("vulxAdvertising")[0].style.top = "475px"; 270 | } 271 | } 272 | }); 273 | } 274 | 275 | var titlesDropdown = document.getElementById("titleDropdown"); 276 | var dropdownSelect = document.getElementById("dropdownButton"); 277 | fetch('https://valorant-api.com/v1/playertitles').then(res => res.json()).then(response => { 278 | response.data = [{ "displayName": "No Title", "uuid": "null" }, ...response.data]; 279 | response.data.forEach(title => { 280 | var dropdownItem = document.createElement("a"); 281 | if (window.playerTitleId == title.uuid) { 282 | dropdownItem.setAttribute("class", "dropdown-item active"); 283 | dropdownSelect.textContent = title.displayName; 284 | } 285 | else { 286 | dropdownItem.setAttribute("class", "dropdown-item"); 287 | dropdownItem.setAttribute("href", "#"); 288 | dropdownItem.setAttribute("data-value", title.uuid); 289 | dropdownItem.addEventListener('click', async (event) => { 290 | selectTitle(title.uuid); 291 | }); 292 | dropdownItem.innerHTML = title.displayName; 293 | titlesDropdown.appendChild(dropdownItem); 294 | } 295 | }); 296 | }); 297 | 298 | fetch('http://127.0.0.1:/timePlaying').then(res => res.json()).then(response => { 299 | var display = document.querySelector('#time'); 300 | function startTimer(display) { 301 | var diff, hours, minutes, seconds; 302 | function timer() { 303 | diff = (((Date.now() - response.time) / 1000) | 0); 304 | 305 | // Setting and displaying hours, minutes, seconds 306 | hours = (diff / 3600) | 0; 307 | minutes = ((diff % 3600) / 60) | 0; 308 | seconds = (diff % 60) | 0; 309 | 310 | hours = hours < 10 ? "0" + hours : hours; 311 | minutes = minutes < 10 ? "0" + minutes : minutes; 312 | seconds = seconds < 10 ? "0" + seconds : seconds; 313 | 314 | display.textContent = hours + ":" + minutes + ":" + seconds; 315 | }; 316 | timer(); 317 | setInterval(timer, 1000); 318 | } 319 | startTimer(display); 320 | }); 321 | 322 | fetch('http://127.0.0.1:/friends').then(res => res.json()).then(response => { 323 | document.querySelector('#friendsCount').textContent = response.friends.length; 324 | }); 325 | 326 | fetch('http://127.0.0.1:/requests').then(res => res.json()).then(response => { 327 | document.querySelector('#requestsCount').textContent = response.count; 328 | }); 329 | 330 | $(window).click(function() { 331 | if(document.getElementById("collapseStatus").classList.contains("show")) { 332 | document.getElementById("collapseStatus").classList.remove("show"); 333 | } 334 | }); 335 | 336 | $('#collapseStatus').click(function(event){ 337 | event.stopPropagation(); 338 | }); 339 | 340 | document.querySelectorAll(".searchBarInput").forEach((inputField) => { 341 | inputField.addEventListener("change", () => { 342 | const name = inputField.getAttribute("name"); 343 | let value = inputField.value; 344 | 345 | //if the value is over 100 characters, we want to trim it down and add an ellipsis 346 | if (value.length > 100) { 347 | value = value.substring(0, 100) + "..."; 348 | } 349 | 350 | const formData = new FormData(); 351 | formData.append(name, value); 352 | 353 | const flagConversion = { 354 | status: 1, 355 | rank: 2, 356 | position: 4, 357 | level: 8, 358 | ally: 16, 359 | enemy: 32, 360 | playerTitleId: 64, 361 | } 362 | 363 | var autosaveJson = { flag: 0 }; 364 | var bodyRes; 365 | 366 | for (const pair of formData.entries()) { 367 | autosaveJson.flag += flagConversion[pair[0]]; 368 | autosaveJson[pair[0]] = pair[1]; 369 | } 370 | 371 | if(name == "systemMessage") { 372 | window.autosaveUrl = "http://127.0.0.1:/sendSystemMessage"; 373 | bodyRes = JSON.stringify({ 374 | message: value, 375 | }); 376 | } else { 377 | bodyRes = JSON.stringify(autosaveJson); 378 | } 379 | 380 | fetch(window.autosaveUrl, { 381 | method: 'POST', 382 | headers: { 383 | 'Accept': 'application/json', 384 | 'Content-Type': 'application/json' 385 | }, 386 | body: bodyRes 387 | }).then(() => { 388 | Notification(true, "Profile updated successfully!"); 389 | getProfile(); 390 | }).catch(() => { 391 | Notification(false, "An error occured while updating your profile!"); 392 | })}); 393 | }); 394 | 395 | function setupSlip(list) { 396 | list.addEventListener('slip:beforewait', function(e){ 397 | if (e.target.classList.contains('instant')) e.preventDefault(); 398 | }, false); 399 | 400 | list.addEventListener('slip:beforeswipe', function(e){ 401 | e.preventDefault(); 402 | }, false); 403 | 404 | list.addEventListener('slip:reorder', function(e){ 405 | e.target.parentNode.insertBefore(e.target, e.detail.insertBefore); 406 | 407 | var olArray = [] 408 | var olChilds = document.getElementById("profileThemes").querySelectorAll('li'); 409 | 410 | olChilds.forEach(child => olArray.push(child.id)) 411 | 412 | return false; 413 | }, false); 414 | 415 | return new Slip(list); 416 | } 417 | setupSlip(document.getElementById('profileThemes')); -------------------------------------------------------------------------------- /public/js/vulx.request.friends.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | function getDifference(array1, array2) { 9 | return array1.filter(object1 => { 10 | return !array2.some(object2 => { 11 | return object1.puuid === object2.puuid; 12 | }); 13 | }); 14 | } 15 | 16 | function getTitleText(title) { 17 | if (!title) return 'Friend'; 18 | 19 | return fetch(`https://valorant-api.com/v1/playertitles/${title}`) 20 | .then(res => res.json()) 21 | .then(res => res.data.titleText); 22 | } 23 | 24 | function getSelfPuuid() { 25 | return fetch('http://127.0.0.1:/userSession') 26 | .then(res => res.json()) 27 | .then(res => res.session.puuid) 28 | } 29 | 30 | function newElement(type, className, id, src, style, textContent) { 31 | var element = document.createElement(type); 32 | element.className = className; 33 | element.id = id; 34 | element.src = src; 35 | element.textContent = textContent; 36 | element.style = style; 37 | return element; 38 | } 39 | 40 | fetch("http://127.0.0.1:/friends") 41 | .then(res => res.json()) 42 | .then(async res => { 43 | var selfPuuid = await getSelfPuuid() 44 | var onlineFriends = res.onlineFriends; 45 | onlineFriends = onlineFriends.filter(friend => friend.private && friend.product == "valorant" && friend.game_name && friend.puuid != selfPuuid); 46 | onlineFriends = onlineFriends.sort((a, b) => a.game_name.localeCompare(b.game_name)); 47 | 48 | var offlineFriends = res.friends; 49 | offlineFriends = offlineFriends.filter(friend => friend.game_name) 50 | offlineFriends = getDifference(offlineFriends, onlineFriends); 51 | offlineFriends = offlineFriends.sort((a, b) => a.game_name.localeCompare(b.game_name)); 52 | 53 | for (var j = 0; j < onlineFriends.length; j++) { 54 | var searchBarResults = document.getElementsByClassName("search-bar-results")[0] 55 | var friendCard = document.createElement("div"); 56 | friendCard.className = "search-bar-results-card"; 57 | friendCard.id = onlineFriends[j].puuid; 58 | searchBarResults.appendChild(friendCard); 59 | 60 | var friendPrivate = JSON.parse(atob(onlineFriends[j].private)); 61 | friendCard.appendChild(newElement("img", "searchPfp", null, `https://media.valorant-api.com/playercards/${friendPrivate.playerCardId}/smallart.png`)); 62 | friendCard.appendChild(newElement("img", "statusIcon", null, `https://cdn.aquaplays.xyz/user/online.png`)); 63 | friendCard.appendChild(newElement("div", "searchBannerCont")); 64 | friendCard.appendChild(newElement("img", "searchBanner", null, `https://media.valorant-api.com/playercards/${friendPrivate.playerCardId}/wideart.png`)); 65 | 66 | var friendSearchInfo = friendCard.appendChild(newElement("div", "searchInfo", "friend-search-info")); 67 | 68 | friendSearchInfo.appendChild(newElement("h1", "themeName-large5 textOverflow", "friend-name", null, null, onlineFriends[j].game_name)); 69 | friendSearchInfo.appendChild(newElement("h3", null, null, null, "font-size: 18px; margin-top: -10px;", await getTitleText(friendPrivate.playerTitleId))) 70 | } 71 | for (var j = 0; j < offlineFriends.length; j++) { 72 | var searchBarResults = document.getElementsByClassName("search-bar-results")[0] 73 | var friendCard = document.createElement("div"); 74 | friendCard.className = "search-bar-results-card"; 75 | searchBarResults.appendChild(friendCard); 76 | 77 | friendCard.appendChild(newElement("img", "searchPfp", null, `https://media.valorant-api.com/playercards/9fb348bc-41a0-91ad-8a3e-818035c4e561/smallart.png`)); 78 | friendCard.appendChild(newElement("img", "statusIcon", null, `https://cdn.aquaplays.xyz/user/offline.png`)); 79 | 80 | var friendBannerContainer = friendCard.appendChild(newElement("div", "searchBannerCont")); 81 | friendBannerContainer.appendChild(newElement("img", "searchBanner", null, `https://media.valorant-api.com/playercards/9fb348bc-41a0-91ad-8a3e-818035c4e561/wideart.png`)); 82 | 83 | var friendSearchInfo = friendCard.appendChild(newElement("div", "searchInfo2", "friend-search-info")); 84 | friendSearchInfo.appendChild(newElement("h1", "themeName-large5 textOverflow", "friend-name", null, null, offlineFriends[j].game_name)); 85 | } 86 | 87 | }).catch((err) => console.log(err)); -------------------------------------------------------------------------------- /public/js/vulx.request.reset.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | async function resetAccount() { 9 | await fetch('http://127.0.0.1:/resetAccount', { 10 | method: 'POST', 11 | headers: { 12 | 'Accept': 'application/json', 13 | 'Content-Type': 'application/json' 14 | }, 15 | body: JSON.stringify( 16 | { 17 | resetAccount: true, 18 | } 19 | ) 20 | }).then(window.location.href = "setup"); 21 | } window.resetAccount = resetAccount; -------------------------------------------------------------------------------- /public/js/vulx.request.session.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | fetch("http://127.0.0.1:/userSession").then(function(response) { 9 | return response.json(); 10 | }).then(function(data) { 11 | if(data.config.firstLaunch == true) { 12 | window.location.href = "setup"; 13 | } 14 | if(data.config.experimental == true) { 15 | document.getElementById("experimentalNav").style.display = "flex"; 16 | } else { 17 | if(!window.location.href.includes("dashboard")) { 18 | window.location.href = "dashboard"; 19 | } 20 | } 21 | if(data.config.webTooltips == true) { 22 | $(document).ready(function(){ 23 | $('[data-toggle="tooltip"]').tooltip({ 24 | trigger : 'hover', 25 | container: 'body' 26 | }); 27 | }); 28 | } 29 | //grabs and sets the session data 30 | document.getElementById("username").textContent = data.session.game_name + "#" + data.session.game_tag; 31 | document.getElementById("usernameNav").textContent = data.session.game_name + "#" + data.session.game_tag; 32 | document.getElementById("connectionLabel").textContent = data.session.resource + " | " + data.session.state; 33 | document.getElementById("accountName").textContent = `Account Name | ${data.session.name.length == 0 ? data.session.game_name : data.session.name}`; 34 | document.getElementById("pid").textContent = "PlayerID | " + data.session.puuid; 35 | document.getElementById("region").textContent = "Region | " + data.session.region; 36 | document.getElementById("port").textContent = "Session Port | " + data.port; 37 | document.getElementById("password").textContent = "Lockpass | " + data.password; 38 | document.getElementById("discordRpc").value = data.config.discordRpc; 39 | document.getElementById("experimentalFeatures").value = data.config.experimental; 40 | document.getElementById("webTooltips").value = data.config.webTooltips; 41 | }).catch(function() { 42 | console.log("Error."); 43 | }); 44 | -------------------------------------------------------------------------------- /public/js/vulx.request.settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | async function getTitleText(title) { 9 | return fetch(`https://valorant-api.com/v1/playertitles/${title}`) 10 | .then(res => res.json()) 11 | .then(res => res.data.titleText); 12 | } 13 | 14 | function resolveIntComma(num) { 15 | return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 16 | } 17 | 18 | function resolveRank(rankId) { 19 | const rankNames = { 20 | "-1": 'norank', 21 | 0: 'unranked', 22 | 1: 'special', 23 | 2: 'special', 24 | 3: 'iron', 25 | 4: 'iron', 26 | 5: 'iron', 27 | 6: 'bronze', 28 | 7: 'bronze', 29 | 8: 'bronze', 30 | 9: 'silver', 31 | 10: 'silver', 32 | 11: 'silver', 33 | 12: 'gold', 34 | 13: 'gold', 35 | 14: 'gold', 36 | 15: 'platinum', 37 | 16: 'platinum', 38 | 17: 'platinum', 39 | 18: 'diamond', 40 | 19: 'diamond', 41 | 20: 'diamond', 42 | 21: 'ascendant', 43 | 22: 'ascendant', 44 | 23: 'ascendant', 45 | 24: 'immortal', 46 | 25: 'immortal', 47 | 26: 'immortal', 48 | 27: 'radiant' 49 | } 50 | return rankNames[rankId]; 51 | } 52 | 53 | function resolveRankNumber(rankId) { 54 | return rankId < 3 ? 0 : (rankId - 2) % 3 == 0 ? 3 : (rankId - 2) % 3; 55 | } 56 | 57 | function getProfile() { 58 | fetch("http://127.0.0.1:/currentSettings").then(function(response) { 59 | return response.json(); 60 | }).then(async function(data) { 61 | //grabs & sets the profile variables 62 | document.getElementById("valorantMatchStatus").textContent = data.queueId; 63 | document.getElementById("valorantStatus").value = data.queueId; 64 | document.getElementById("valorantLeaderboard").value = data.leaderboardPosition; 65 | document.getElementById("valorantLevel").value = data.accountLevel; 66 | document.getElementById("ally").textContent = data.partyOwnerMatchScoreAllyTeam; 67 | document.getElementById("valorantAlly").value = data.partyOwnerMatchScoreAllyTeam; 68 | document.getElementById("enemy").textContent = data.partyOwnerMatchScoreEnemyTeam; 69 | document.getElementById("valorantEnemy").value = data.partyOwnerMatchScoreEnemyTeam; 70 | document.getElementById("activity").src = `https://cdn.aquaplays.xyz/user/${data.status}.png`; 71 | document.getElementById("playerCard").src = `https://media.valorant-api.com/playercards/${data.playerCardId}/wideart.png`; 72 | document.getElementById("playerCardSmall").src = `https://media.valorant-api.com/playercards/${data.playerCardId}/smallart.png`; 73 | document.getElementById("valorantRankImg").src = `https://cdn.aquaplays.xyz/ranks/${data.competitiveTier < 3 ? 0 : data.competitiveTier}.png`; 74 | 75 | window.playerTitleId = data.playerTitleId; 76 | 77 | const rank = document.getElementById("valorantRank") 78 | rank.innerHTML = ''; 79 | 80 | const rankTitleSpan = document.createElement("span"); 81 | rankTitleSpan.setAttribute("data-i18n", `ranks.${resolveRank(data.competitiveTier)}`); 82 | rank.appendChild(rankTitleSpan); 83 | 84 | if (data.competitiveTier > 0 && data.competitiveTier < 27) { 85 | const rankNumberSpan = document.createElement("span"); 86 | rankNumberSpan.innerText = ` ${resolveRankNumber(data.competitiveTier)}`; 87 | rank.appendChild(rankNumberSpan); 88 | } 89 | 90 | if (data.leaderboardPosition) { 91 | const rankPositionSpan = document.createElement("span"); 92 | rankPositionSpan.innerText = ` #${resolveIntComma(data.leaderboardPosition)}`; 93 | rank.appendChild(rankPositionSpan); 94 | } 95 | 96 | document.getElementById("valorantTitle").textContent = await getTitleText(data.playerTitleId); 97 | 98 | $('body').localize(); 99 | 100 | }).catch(function(error) { 101 | console.log(error); 102 | }); 103 | } window.getProfile = getProfile; 104 | 105 | getProfile(); 106 | -------------------------------------------------------------------------------- /public/js/vulx.search.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | function searchFriends() { 9 | var input = document.getElementById("searchFriends"); 10 | var friends = document.getElementsByClassName("search-bar-results")[0].children; 11 | 12 | for (var i = 0; i < friends.length; i++) { 13 | var friend = friends[i]; 14 | var friendSearchInfo = friend.querySelector("#friend-search-info"); 15 | var friendName = friendSearchInfo.querySelector("#friend-name").textContent; 16 | friendName.toUpperCase().indexOf(input.value.toUpperCase()) > -1 ? friend.style.display = "" : friend.style.display = "none"; 17 | } 18 | } -------------------------------------------------------------------------------- /public/js/vulx.welcome.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | fetch("http://127.0.0.1:/userSession").then(function(response) { 9 | return response.json(); 10 | }).then(function(data) { 11 | if(data.config.firstLaunch == false && window.location.href != "http://127.0.0.1/dashboard") window.location.href = "dashboard"; 12 | document.getElementById("username").textContent = data.session.game_name; 13 | }).catch(function() { 14 | console.log("Error loading user session"); 15 | }); 16 | 17 | let discordRpc = false; 18 | let testFeatures = false; 19 | 20 | function stepOne() { 21 | document.getElementById("welcomeStepOne").style.display = "none"; 22 | document.getElementById("welcomeStepTwo").style.display = "flex"; 23 | } 24 | function stepTwo(value) { 25 | discordRpc = value; 26 | document.getElementById("welcomeStepTwo").style.display = "none"; 27 | document.getElementById("welcomeStepThree").style.display = "flex"; 28 | } 29 | function stepThree(value) { 30 | testFeatures = value; 31 | document.getElementById("welcomeStepThree").style.display = "none"; 32 | document.getElementById("welcomeStepFour").style.display = "flex"; 33 | } 34 | function stepFour() { 35 | document.getElementById("welcomeStepFour").style.display = "none"; 36 | postSettings(); 37 | window.location.href = "dashboard"; 38 | } 39 | 40 | function postSettings() { 41 | fetch('http://127.0.0.1:/updateSettings', { 42 | method: 'POST', 43 | headers: { 44 | 'Accept': 'application/json', 45 | 'Content-Type': 'application/json' 46 | }, 47 | body: JSON.stringify( 48 | { 49 | updateType: "settingsWelcome", 50 | firstLaunch: false, 51 | data: { 52 | discordRpc: discordRpc, 53 | testFeatures: testFeatures 54 | } 55 | } 56 | ) 57 | }); 58 | } -------------------------------------------------------------------------------- /public/json/ranks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": -1, 4 | "No Rank": [ 5 | -1 6 | ] 7 | }, 8 | { 9 | "id": 1, 10 | "Special": [ 11 | 1, 12 | 2 13 | ] 14 | }, 15 | { 16 | "id": 0, 17 | "Unranked": [ 18 | 0 19 | ] 20 | }, 21 | { 22 | "id": 3, 23 | "Iron": [ 24 | 3, 25 | 4, 26 | 5 27 | ] 28 | }, 29 | { 30 | "id": 6, 31 | "Bronze": [ 32 | 6, 33 | 7, 34 | 8 35 | ] 36 | }, 37 | { 38 | "id": 9, 39 | "Silver": [ 40 | 9, 41 | 10, 42 | 11 43 | ] 44 | }, 45 | { 46 | "id": 12, 47 | "Gold": [ 48 | 12, 49 | 13, 50 | 14 51 | ] 52 | }, 53 | { 54 | "id": 15, 55 | "Platinum": [ 56 | 15, 57 | 16, 58 | 17 59 | ] 60 | }, 61 | { 62 | "id": 18, 63 | "Diamond": [ 64 | 18, 65 | 19, 66 | 20 67 | ] 68 | }, 69 | { 70 | "id": 21, 71 | "Ascendant": [ 72 | 21, 73 | 22, 74 | 23 75 | ] 76 | }, 77 | { 78 | "id": 24, 79 | "Immortal": [ 80 | 24, 81 | 25, 82 | 26 83 | ] 84 | }, 85 | { 86 | "id": 27, 87 | "Radiant": [ 88 | 27 89 | ] 90 | } 91 | ] -------------------------------------------------------------------------------- /public/locales/en/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "loading": { 3 | "profile": "Loading Profile..." 4 | }, 5 | "input": { 6 | "searchFriends": "Search Friends..." 7 | }, 8 | "navbar": { 9 | "presence": "Presence", 10 | "settings": "Settings", 11 | "sessionStats": "Session Stats" 12 | }, 13 | "tooltip": { 14 | "banner": "This lets you change your Valorant card.", 15 | "activity": "This lets you change your Valorant activity.", 16 | "availableActivity": "Changes your activity to available (removes custom status).", 17 | "onlineActivity": "Changes your activity to online (display custom status).", 18 | "awayActivity": "Changes your activity to away (removes custom status).", 19 | "streamActivity": "Changes your activity to purple (glitch visuals).", 20 | "dndActivity": "Changes your activity to Do Not Disturb (removes custom status).", 21 | "offlineActivity": "Changes your activity to offline (hides you offline).", 22 | "title": "Note: This value will only show for friends that are not in your party (this will NOT show on your client!).", 23 | "level": "Note: This value will only show for friends that are not in your party (this will NOT show on your client!).", 24 | "systemMessage": "This will send a system message in your party (client sided only).", 25 | "noRank": "Use this to remove your rank from your profile completely.", 26 | "specialRank": "This rank is usually unavailable." 27 | }, 28 | "activity": { 29 | "available": "Available", 30 | "online": "Online", 31 | "away": "Away", 32 | "stream": "Stream", 33 | "dnd": "DND", 34 | "offline": "Offline" 35 | }, 36 | "highlight": { 37 | "statistics": "Statistics", 38 | "friends": "Friends", 39 | "requests": "Requests", 40 | "timeElapsed": "Time Elapsed" 41 | }, 42 | "rankInfo": { 43 | "rank": "Rank" 44 | }, 45 | "status": { 46 | "status": "Status", 47 | "profileStatus": "Profile Status", 48 | "profileStatusPlaceholder": "Enter some text...", 49 | "scoreAlly": "Score Ally", 50 | "scoreAllyPlaceholder": "Enter a number...", 51 | "scoreEnemy": "Score Enemy", 52 | "scoreEnemyPlaceholder": "Enter a number..." 53 | }, 54 | "account": { 55 | "account": "Status", 56 | "title": "Profile Title", 57 | "leaderboard": "Leaderboard", 58 | "leaderboardPlaceholder": "Enter a number to be your position...", 59 | "level": "Account Level", 60 | "levelPlaceholder": "Enter a number..." 61 | }, 62 | "systemMessage": { 63 | "systemMessage": "System Message", 64 | "message": "Message", 65 | "messagePlaceholder": "Enter some text..." 66 | }, 67 | "placeholder": { 68 | "noUserFound": "Valorant Player" 69 | }, 70 | "advertising": { 71 | "text": "Made with ♡ by" 72 | }, 73 | "footer": { 74 | "text": "Vulx Profile Editor - Made with ♡ by" 75 | }, 76 | "debugModal": { 77 | "sessionDebug": "Session Debug", 78 | "close": "Close" 79 | }, 80 | "settings": { 81 | "settings": "Account Settings", 82 | "close": "Close", 83 | "save": "Save", 84 | "experimental": "Experimental Features", 85 | "discord": "Discord RPC", 86 | "tooltips": "Tool Tips", 87 | "yes": "Yes", 88 | "no": "No", 89 | "reset": "Reset config" 90 | }, 91 | "ranks": { 92 | "norank": "No Rank", 93 | "special": "Special", 94 | "unranked": "Unranked", 95 | "iron": "Iron", 96 | "bronze": "Bronze", 97 | "silver": "Silver", 98 | "gold": "Gold", 99 | "platinum": "Platinum", 100 | "diamond": "Diamond", 101 | "ascendant": "Ascendant", 102 | "immortal": "Immortal", 103 | "radiant": "Radiant" 104 | } 105 | } -------------------------------------------------------------------------------- /public/locales/ru/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "loading": { 3 | "profile": "Загрузка профиля..." 4 | }, 5 | "input": { 6 | "searchFriends": "Найти друзей..." 7 | }, 8 | "navbar": { 9 | "presence": "Профиль", 10 | "settings": "Настройки", 11 | "sessionStats": "Статистика сессии" 12 | }, 13 | "tooltip": { 14 | "banner": "Это позволяет вам поменять баннер профиля.", 15 | "activity": "Это позволяет вам поменять активность профиля.", 16 | "availableActivity": "Изменяет вашу активность на \"Доступен\" (удаляет пользовательский статус).", 17 | "onlineActivity": "Изменяет вашу активность на \"Онлайн\" (удаляет пользовательский статус).", 18 | "awayActivity": "Изменяет вашу активность на \"Недоступен\" (удаляет пользовательский статус).", 19 | "streamActivity": "Изменяет вашу активность на фиолетовую (сломанный визуальный эффект).", 20 | "dndActivity": "Изменяет вашу активность на \"Не беспокоить\" (удаляет пользовательский статус).", 21 | "offlineActivity": "Изменяет вашу активность на \"Не в сети\" (скрывает вас).", 22 | "title": "Примечание: Это значение будет отображаться только для друзей, которые не находятся в вашем отряде (это НЕ будет отображаться в игре!).", 23 | "level": "Примечание: Это значение будет отображаться только для друзей, которые не находятся в вашем отряде (это НЕ будет отображаться в игре!).", 24 | "systemMessage": "Это отправит системное сообщение в чат отряда (только у локального игрока).", 25 | "noRank": "Используйте это, чтобы полностью удалить свое звание из вашего профиля.", 26 | "specialRank": "Это звание обычно недоступно." 27 | }, 28 | "activity": { 29 | "available": "Доступен", 30 | "online": "В сети", 31 | "away": "Недоступен", 32 | "stream": "Стрим", 33 | "dnd": "Не беспокоить", 34 | "offline": "Не в сети" 35 | }, 36 | "highlight": { 37 | "statistics": "Статистика", 38 | "friends": "Друзья", 39 | "requests": "Запросы", 40 | "timeElapsed": "Прошло времени" 41 | }, 42 | "rankInfo": { 43 | "rank": "Ранг" 44 | }, 45 | "status": { 46 | "status": "Статус", 47 | "profileStatus": "Статус профиля", 48 | "profileStatusPlaceholder": "Введите текст...", 49 | "scoreAlly": "Счет союзников", 50 | "scoreAllyPlaceholder": "Введите число...", 51 | "scoreEnemy": "Счет врагов", 52 | "scoreEnemyPlaceholder": "Введите число..." 53 | }, 54 | "account": { 55 | "account": "Статус", 56 | "title": "Титул профиля", 57 | "leaderboard": "Лидеры", 58 | "leaderboardPlaceholder": "Введите число, которое будет вашей позицией...", 59 | "level": "Уровень аккаунта", 60 | "levelPlaceholder": "Введите число..." 61 | }, 62 | "systemMessage": { 63 | "systemMessage": "Системное сообщение", 64 | "message": "Сообщение", 65 | "messagePlaceholder": "Введите текст..." 66 | }, 67 | "placeholder": { 68 | "noUserFound": "Игрок Valorant" 69 | }, 70 | "advertising": { 71 | "text": "Сделано с любовью," 72 | }, 73 | "footer": { 74 | "text": "Редактор профиля Vulx - Сделан с любовью," 75 | }, 76 | "debugModal": { 77 | "sessionDebug": "Отладка сессии", 78 | "close": "Закрыть" 79 | }, 80 | "settings": { 81 | "settings": "Настройки аккаунта", 82 | "close": "Закрыть", 83 | "save": "Сохранить", 84 | "experimental": "Экспериментальные функции", 85 | "discord": "Discord RPC", 86 | "tooltips": "Подсказки", 87 | "yes": "Да", 88 | "no": "Нет", 89 | "reset": "Перезагрузить конфиг" 90 | }, 91 | "ranks": { 92 | "norank": "Без ранга", 93 | "special": "Особый", 94 | "unranked": "Без звания", 95 | "iron": "Железо", 96 | "bronze": "Бронза", 97 | "silver": "Серебро", 98 | "gold": "Золото", 99 | "platinum": "Платина", 100 | "diamond": "Алмаз", 101 | "ascendant": "Расцвет", 102 | "immortal": "Бессмертный", 103 | "radiant": "Радиант" 104 | } 105 | } -------------------------------------------------------------------------------- /public/welcome.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Vulx - Valorant 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |

VULX

32 |

Valorant Profile Editor

33 |
34 |
35 |

Hey , Welcome to Vulx

36 |

Before we begin lets go over some basic configuration steps

37 | 40 |
41 |
42 | 58 | 74 | 93 |
94 |
95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /routes/experimentsRouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const express = require('express'); 9 | const experimentsController = require('../controllers/experimentsController'); 10 | 11 | const router = express.Router(); 12 | 13 | router 14 | .route('/updateExperiments') 15 | .post(experimentsController.updateExperiments); 16 | 17 | router 18 | .route('/currentExperiments') 19 | .get(experimentsController.currentExperiments); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /routes/gameRouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const express = require('express'); 9 | const gameController = require('../controllers/gameController'); 10 | 11 | const router = express.Router(); 12 | 13 | router 14 | .route('/sendSystemMessage') 15 | .post(gameController.sendSystemMessage); 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const express = require('express'); 9 | const profileRouter = require('./profileRouter'); 10 | const gameRouter = require('./gameRouter'); 11 | const webRouter = require('./webRouter'); 12 | 13 | const router = express.Router(); 14 | 15 | const defaultRoutes = [ 16 | { 17 | path: '/', 18 | route: profileRouter, 19 | }, 20 | { 21 | path: '/', 22 | route: gameRouter, 23 | }, 24 | { 25 | path: '/', 26 | route: webRouter, 27 | }, 28 | ]; 29 | 30 | defaultRoutes.forEach((route) => { 31 | router.use(route.path, route.route); 32 | }); 33 | 34 | router.use('/locales', express.static('public/locales')) 35 | 36 | module.exports = router; -------------------------------------------------------------------------------- /routes/profileRouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const express = require('express'); 9 | const profileController = require('../controllers/profileController'); 10 | const presenceController = require('../controllers/presenceController'); 11 | 12 | const router = express.Router(); 13 | 14 | router 15 | .route('/userSession') 16 | .get(profileController.userSession); 17 | 18 | router 19 | .route('/updateSettings') 20 | .post(profileController.updateSettings); 21 | 22 | router 23 | .route('/resetAccount') 24 | .post(profileController.resetAccount); 25 | 26 | router 27 | .route('/updateStatus') 28 | .post(profileController.updateStatus); 29 | 30 | router 31 | .route('/updatePresence') 32 | .post(presenceController.updatePresence); 33 | 34 | router 35 | .route('/currentSettings') 36 | .get(presenceController.currentSettings); 37 | 38 | router 39 | .route('/friends') 40 | .get(profileController.getFriends); 41 | 42 | router 43 | .route('/timePlaying') 44 | .get(profileController.timePlaying); 45 | 46 | router 47 | .route('/requests') 48 | .get(profileController.getRequestsCount); 49 | 50 | module.exports = router; -------------------------------------------------------------------------------- /routes/webRouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const express = require('express'); 9 | const webController = require('../controllers/webController'); 10 | 11 | const router = express.Router(); 12 | 13 | router 14 | .route('/dashboard') 15 | .get(webController.dashboard); 16 | 17 | router 18 | .route('/setup') 19 | .get(webController.setup); 20 | 21 | router 22 | .route('/') 23 | .get(webController.info); 24 | 25 | router 26 | .route('/user/:puuid') 27 | .get(webController.user); 28 | 29 | module.exports = router; -------------------------------------------------------------------------------- /utils/FriendHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const AxiosHelper = require('./AxiosHelper'); 9 | 10 | class FHelper { 11 | constructor() { 12 | this.friends, this.presences, this.vulxAxios; 13 | } 14 | 15 | async _initialize() { 16 | if (!this.initializePromise) { 17 | this.initializePromise = await this._doInitialize(); 18 | } 19 | 20 | return this.initializePromise; 21 | } 22 | 23 | async _doInitialize() { 24 | await this._initializeVulxAxios(); 25 | } 26 | 27 | async _initializeVulxAxios() { 28 | this.vulxAxios = await AxiosHelper.getVulxAxios(); 29 | } 30 | 31 | async getFriends() { 32 | await this._initialize(); 33 | 34 | const friends = await this.vulxAxios.get(`/chat/v4/friends`).then(res => res.data.friends); 35 | this.friends = friends; 36 | return friends; 37 | } 38 | 39 | async getPresences() { 40 | await this._initialize(); 41 | 42 | const presences = await this.vulxAxios.get(`/chat/v4/presences`).then(res => res.data.presences); 43 | this.presences = presences; 44 | return presences; 45 | } 46 | } 47 | 48 | module.exports = new FHelper(); -------------------------------------------------------------------------------- /utils/LookupAPI.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const axios = require('axios'); 9 | 10 | // unused but might be useful in the future 11 | class User { 12 | constructor(puuid, name, teamID, accountLevel, character, playerCard, playerTitle, skins) { 13 | this.puuid = puuid; 14 | this.name = name; 15 | this.teamID = teamID; 16 | this.accountLevel = accountLevel; 17 | this.character = character; 18 | this.playerCard = playerCard; 19 | this.playerTitle = playerTitle; 20 | this.skins = skins; 21 | } 22 | } 23 | 24 | class lookup { 25 | constructor() { 26 | this.weaponSkins = null; 27 | this.agents = null; 28 | } 29 | 30 | // initialization functions 31 | async _doInitialize() { 32 | await this._initializeWeaponSkins(); 33 | await this._initializeAgents(); 34 | } 35 | 36 | async _initialize() { 37 | if(!this.initializationPromise) { 38 | this.initializationPromise = this._doInitialize(); 39 | } 40 | return this.initializationPromise; 41 | } 42 | 43 | async _initializeWeaponSkins() { 44 | this.weaponSkins = await axios.get('https://valorant-api.com/v1/weapons/skins').then(res => res.data.data); 45 | } 46 | 47 | async _initializeAgents() { 48 | this.agents = await axios.get('https://valorant-api.com/v1/agents').then(res => res.data.data); 49 | } 50 | 51 | // public functions 52 | async getWeaponSkins() { 53 | await this._initialize(); 54 | return this.weaponSkins; 55 | } 56 | 57 | async getAgents() { 58 | await this._initialize(); 59 | return this.agents; 60 | } 61 | } 62 | 63 | module.exports = new lookup(); -------------------------------------------------------------------------------- /utils/SystemMessageHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const AxiosHelper = require("./AxiosHelper"); 9 | const Logger = require("./Logger"); 10 | 11 | class SystemMessageHelper { 12 | constructor() { } 13 | 14 | async _doInitialize() { 15 | await this._initializeVulxAxios(); 16 | } 17 | 18 | async _initialize() { 19 | if(!this.initializationPromise) { 20 | this.initializationPromise = this._doInitialize(); 21 | } 22 | return this.initializationPromise; 23 | } 24 | 25 | async _initializeVulxAxios() { 26 | this.vulxAxios = await AxiosHelper.getVulxAxios(); 27 | } 28 | 29 | async sendSystemMessage(message) { 30 | await this._initialize(); 31 | const conversations = await this.vulxAxios.get("/chat/v6/conversations").then(res => res.data); 32 | await this.vulxAxios.post("/chat/v6/messages", { 33 | cid: conversations.conversations[0].cid, 34 | type: "system", // suprised this is undocumented, ok thanks, this is now kyles, yoink, k bi thx x 35 | message 36 | }).catch(() => Logger.error("Failed to send system message")); 37 | } 38 | } 39 | 40 | module.exports = new SystemMessageHelper(); -------------------------------------------------------------------------------- /utils/ValorantAPI.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | // library definitions 9 | const axios = require('axios'); 10 | 11 | // local definitions 12 | const AxiosHelper = require('./AxiosHelper'); 13 | const Logger = require('./Logger'); 14 | 15 | class Client { 16 | constructor(entitlementToken, accessToken) { 17 | this.region, this.puuid, this.gameName, this.gameTag, this.clientVersion = null; 18 | this.entitlementToken = entitlementToken; 19 | this.accessToken = accessToken; 20 | this.platform = "ew0KCSJwbGF0Zm9ybVR5cGUiOiAiUEMiLA0KCSJwbGF0Zm9ybU9TIjogIldpbmRvd3MiLA0KCSJwbGF0Zm9ybU9TVmVyc2lvbiI6ICIxMC4wLjE5MDQyLjEuMjU2LjY0Yml0IiwNCgkicGxhdGZvcm1DaGlwc2V0IjogIlVua25vd24iDQp9"; 21 | 22 | const axiosInstance = axios.create(); 23 | 24 | // request interceptor 25 | axiosInstance.interceptors.request.use(this._handleConfig, this._handleReqError); 26 | 27 | // response interceptor 28 | axiosInstance.interceptors.response.use(this._handleSuccess, this._handleResError); 29 | 30 | this.axios = axiosInstance; 31 | 32 | this.vulxAxios; 33 | } 34 | 35 | // axios interceptor functions 36 | _handleConfig = (config) => { 37 | config.headers = { 38 | 'X-Riot-Entitlements-JWT': this.entitlementToken || '', 39 | 'Authorization': `Bearer ${this.accessToken}`, 40 | 'X-Riot-ClientVersion': this.clientVersion, 41 | 'X-Riot-ClientPlatform': this.platform 42 | } 43 | return config; 44 | } 45 | 46 | _handleReqError = (error) => { 47 | return Promise.reject(error) 48 | } 49 | 50 | _handleSuccess = (response) => { 51 | return response; 52 | } 53 | 54 | _handleResError = (error) => { 55 | const originalRequest = error.config; 56 | if (error.response.status === 400) { 57 | this._refreshEntitlement(); 58 | Logger.info("Refreshing entitlements..."); 59 | return this.axios(originalRequest); 60 | } else if (error.response.status === 404) { 61 | return this.axios(originalRequest); 62 | } 63 | return Promise.reject(error) 64 | } 65 | 66 | // initialization functions 67 | async _doInitialize() { 68 | await this._initializeVulxAxios(); 69 | await this._initializeSession(); 70 | await this._initializeServiceURLs(); 71 | await this._initializeAuth(); 72 | await this._initializeVersion(); 73 | await this._initializeUserInfo(); 74 | 75 | //await this.vulxAxios.get('/chat/v1/session').then(res => console.log(res.data)) 76 | } 77 | 78 | async _initialize() { 79 | if(!this.initializationPromise) { 80 | this.initializationPromise = this._doInitialize(); 81 | } 82 | return this.initializationPromise; 83 | } 84 | 85 | async _initializeUserInfo() { 86 | this.userInfo = await this.vulxAxios.get('/chat/v1/session').then(res => res.data).catch(this._initializeUserInfo); 87 | this.gameName = this.userInfo.game_name; 88 | this.gameTag = this.userInfo.game_tag; 89 | } 90 | 91 | async _initializeVulxAxios() { 92 | this.vulxAxios = await AxiosHelper.getVulxAxios() 93 | } 94 | 95 | async _initializeSession() { //(phase) displays the current phase of the game (Pending, Idle, Gameplay) 96 | const externalSession = await this._getExternalSession(); 97 | 98 | externalSession.launchConfiguration.arguments.forEach(arg => { 99 | if(arg.includes("-ares-deployment")) { 100 | this.region = arg.split("=")[1]; 101 | } else if (arg.includes("-subject")) { 102 | this.puuid = arg.split("=")[1]; 103 | } else if (arg.includes("-config-endpoint")) { 104 | this.configEndpoint = arg.split("=")[1]; 105 | } 106 | }); 107 | Logger.debug(`Got external session; Region: ${this.region} PUUID: ${this.puuid}`); 108 | } 109 | 110 | async _initializeServiceURLs() { 111 | const res = await this.axios.get(`${this.configEndpoint}/v1/config/${this.region}`).then(res => res.data); 112 | this.coreGameURL = res.Collapsed.SERVICEURL_COREGAME; 113 | this.playerURL = res.Collapsed.SERVICEURL_NAME; 114 | } 115 | 116 | async _initializeAuth() { 117 | await this._refreshEntitlement(); 118 | } 119 | 120 | async _initializeVersion() { 121 | const res = await this.axios.get(`${this.coreGameURL}/session/v1/sessions/${this.puuid}`).then(res => res.data); 122 | this.clientVersion = await res.clientVersion; 123 | } 124 | 125 | // internal use functions 126 | async _getExternalSession() { 127 | const res = await this.vulxAxios.get("/product-session/v1/external-sessions").catch(err => Logger.debug('API response error getting external session.')); 128 | 129 | if (!res || !res.data || Object.keys(res.data).length == 0) { 130 | Logger.debug("Failed to get external session, retrying..."); 131 | await new Promise(resolve => setTimeout(resolve, 1000)); 132 | return await this._getExternalSession(); 133 | } 134 | return await res.data[Object.keys(res.data)[1]]; 135 | } 136 | async _refreshEntitlement() { 137 | const response = await this.vulxAxios.get("/entitlements/v1/token"); 138 | this.entitlementToken = response.data.token; 139 | this.accessToken = response.data.accessToken; 140 | //Logger.debug(`Entitlement token refreshed: ${this.entitlementToken}`); 141 | //Logger.debug(`Access token refreshed: ${this.accessToken}`); 142 | return true; 143 | } 144 | 145 | async _getPlayerLoadout() { 146 | const res = await this.axios.get(`${this.playerURL}/personalization/v2/players/${this.puuid}/playerloadout`).then(res => res.data); 147 | return await res; 148 | } 149 | 150 | async _putPlayerLoadout(loadout) { 151 | await this.axios.put(`${this.playerURL}/personalization/v2/players/${this.puuid}/playerloadout`, loadout); 152 | } 153 | 154 | // public functions 155 | async getPUUID() { 156 | await this._initialize(); 157 | return this.puuid; 158 | } 159 | 160 | async getRegion() { 161 | await this._initialize(); 162 | return this.region; 163 | } 164 | 165 | async getGameName() { 166 | await this._initialize(); 167 | return this.gameName; 168 | } 169 | 170 | async getGameTag() { 171 | await this._initialize(); 172 | return this.gameTag; 173 | } 174 | 175 | async updatePlayerLoadout(accountLevel, playerCardId, playerTitleId) { 176 | await this._initialize(); 177 | let loadout = await this._getPlayerLoadout(); 178 | loadout.Identity.AccountLevel = accountLevel; 179 | loadout.Identity.PlayerCardID = playerCardId; 180 | loadout.Identity.PlayerTitleID = playerTitleId; 181 | await this._putPlayerLoadout(loadout); 182 | } 183 | 184 | // value accessors 185 | async getClientVersion() { 186 | await this._initialize(); 187 | return await this.clientVersion; 188 | } 189 | 190 | async getUserInfo() { 191 | await this._initialize(); 192 | return await this.userInfo; 193 | } 194 | } 195 | 196 | module.exports = new Client(); -------------------------------------------------------------------------------- /utils/axiosHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const axios = require('axios'); 9 | const https = require('https'); 10 | 11 | const LockFile = require('./lockfile'); 12 | 13 | class Helper { 14 | constructor() { 15 | const vulxAxios = axios.create({ 16 | timeout: 1000, 17 | headers: { 18 | common: { 19 | 'User-Agent': 'ShooterGame/8 Windows/10.0.19042.1.768.64bit', 20 | 'X-Riot-ClientPlatform': 'ew0KCSJwbGF0Zm9ybVR5cGUiOiAiUEMiLA0KCSJwbGF0Zm9ybU9TIjogIldpbmRvd3MiLA0KCSJwbGF0Zm9ybU9TVmVyc2lvbiI6ICIxMC4wLjE5MDQyLjEuNzY4LjY0Yml0IiwNCgkicGxhdGZvcm1DaGlwc2V0IjogIlVua25vd24iDQp9', 21 | 'X-Riot-ClientVersion': 'release-04.07-shipping-13-697073', 22 | 'Content-Type': 'application/json' 23 | }, 24 | put: { 25 | 'Content-Type': 'application/json' 26 | } 27 | }, 28 | httpsAgent: new https.Agent({ 29 | rejectUnauthorized: false 30 | }) 31 | }); 32 | 33 | vulxAxios.interceptors.request.use(function(config) { 34 | config.baseURL = `https://127.0.0.1:${LockFile.port}`; 35 | config.headers.common['Authorization'] = 'Basic ' + Buffer.from(`riot:${LockFile.password}`).toString('base64'); 36 | 37 | return config; 38 | }) 39 | 40 | vulxAxios.interceptors.response.use(function (response) { 41 | return response; 42 | }, function (error) { 43 | const originalRequest = error.config; 44 | if (error.response && error.response.status === 401) { 45 | LockFile._initializeLockFile(); 46 | this._doInitialize(); 47 | return this.axios(originalRequest); 48 | } 49 | 50 | return Promise.reject(error); 51 | }); 52 | 53 | this.axios = vulxAxios; 54 | } 55 | 56 | // initialization functions 57 | async _doInitialize() { 58 | await this._initializeLockfile(); 59 | await this._initializeChatSession(); 60 | } 61 | 62 | async _initialize() { 63 | if(!this.initializationPromise) { 64 | this.initializationPromise = this._doInitialize(); 65 | } 66 | return this.initializationPromise; 67 | } 68 | 69 | async _initializeLockfile() { 70 | await LockFile.getLockfile(); 71 | } 72 | 73 | async _initializeChatSession() { 74 | const response = await this.axios.get('/chat/v1/session').then(res => res.data).catch(this._initializeChatSession); 75 | if (response.loaded === true && response.state === 'connected') return true; 76 | else return this._initializeChatSession(); 77 | } 78 | 79 | async getVulxAxios() { 80 | await this._initialize(); 81 | return this.axios; 82 | } 83 | } 84 | 85 | module.exports = new Helper(); -------------------------------------------------------------------------------- /utils/catchAsync.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const catchAsync = (fn) => (req, res, next) => { 9 | Promise.resolve(fn(req, res, next)).catch((err) => next(err)); 10 | }; 11 | 12 | module.exports = catchAsync; -------------------------------------------------------------------------------- /utils/configHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const fs = require('fs'); 9 | const { homedir } = require('os'); 10 | 11 | const vulxConfigPath = `${homedir()}/AppData/Local/ProjectX/config/`; 12 | 13 | class Helper { 14 | constructor() { 15 | this.valorantConfig; 16 | this.leagueConfig; 17 | this.vulxConfig; 18 | this.experimentsConfig; 19 | } 20 | 21 | // initialization functions 22 | async _doInitialize() { 23 | await this._initializeDirectory(); 24 | await this._initializeConfig(); 25 | } 26 | 27 | async _initialize() { 28 | if(!this.initializationPromise) { 29 | this.initializationPromise = this._doInitialize(); 30 | } 31 | return this.initializationPromise; 32 | } 33 | 34 | async _initializeConfig() { 35 | this.valorantConfig = await this._getAndCreateIfNotExistsValorantConfig(); 36 | this.leagueConfig = await this._getAndCreateIfNotExistsLeagueConfig(); 37 | this.vulxConfig = await this._getAndCreateIfNotExistsVulxConfig(); 38 | this.experimentsConfig = await this._getAndCreateIfNotExistsExperimentsConfig(); 39 | } 40 | 41 | async _initializeDirectory() { 42 | if (!fs.existsSync(vulxConfigPath)) { 43 | fs.mkdirSync(vulxConfigPath, { recursive: true }); 44 | } 45 | } 46 | 47 | async _getAndCreateIfNotExistsValorantConfig() { 48 | if (!fs.existsSync(vulxConfigPath + 'valorant.json')) { 49 | await this._createValorantConfig(); 50 | } else { 51 | await this._getValorantConfig(); 52 | } 53 | 54 | return this.valorantConfig; 55 | } 56 | 57 | async _getAndCreateIfNotExistsLeagueConfig() { 58 | if (!fs.existsSync(vulxConfigPath + 'league.json')) { 59 | await this._createLeagueConfig(); 60 | } else { 61 | await this._getLeagueConfig(); 62 | } 63 | 64 | return this.leagueConfig; 65 | } 66 | 67 | async _getAndCreateIfNotExistsVulxConfig() { 68 | if (!fs.existsSync(vulxConfigPath + 'vulx.json')) { 69 | await this._createVulxConfig(); 70 | } else { 71 | await this._getVulxConfig(); 72 | } 73 | 74 | return this.vulxConfig; 75 | } 76 | 77 | async _getAndCreateIfNotExistsExperimentsConfig() { 78 | if (!fs.existsSync(vulxConfigPath + 'experiments.json')) { 79 | await this._createExperimentsConfig(); 80 | } else { 81 | await this._getExperimentsConfig(); 82 | } 83 | 84 | return this.experimentsConfig; 85 | } 86 | 87 | async _createValorantConfig() { 88 | const config = { 89 | isValid:true, 90 | sessionLoopState:'INGAME', 91 | partyOwnerSessionLoopState:'INGAME', 92 | customGameName:'', 93 | customGameTeam:'', 94 | partyOwnerMatchMap:'', 95 | partyOwnerMatchCurrentTeam:'', 96 | partyOwnerMatchScoreAllyTeam:0, 97 | partyOwnerMatchScoreEnemyTeam:0, 98 | partyOwnerProvisioningFlow:'Invalid', 99 | provisioningFlow:'Invalid', 100 | matchMap:'', 101 | partyId:'727', 102 | isPartyOwner:true, 103 | partyState:'DEFAULT', 104 | maxPartySize:5, 105 | queueId:'Vulx - Valorant Profile Editor', 106 | partyLFM:false, 107 | partySize:1, 108 | tournamentId:'', 109 | rosterId:'', 110 | partyVersion:1650719279092, 111 | queueEntryTime:'0001.01.01-00.00.00', 112 | playerCardId:'30b64514-440d-1261-f863-6bbb180263f9', 113 | playerTitleId:'00d4d326-4edc-3229-7c28-129d3374e3ad', 114 | preferredLevelBorderId:'', 115 | accountLevel:727, 116 | competitiveTier:23, 117 | leaderboardPosition:0, 118 | isIdle:true 119 | } 120 | 121 | await fs.writeFileSync(vulxConfigPath + "valorant.json", JSON.stringify(config)); 122 | this.valorantConfig = config; 123 | } 124 | 125 | async _createLeagueConfig() { 126 | const config = { 127 | "championId":"25", 128 | "companionId":"15008", 129 | "damageSkinId":"1", 130 | "gameId":"5840315011", 131 | "gameMode":"CLASSIC", 132 | "gameQueueType":"NORMAL", 133 | "gameStatus":"inGame", 134 | "iconOverride":"", 135 | "isObservable":"ALL", 136 | "level":"167", 137 | "mapId":"11", 138 | "mapSkinId":"55", 139 | "masteryScore":"357", 140 | "profileIcon":"1", 141 | "puuid":"a8e43daa-f78c-516b-871c-565503dd9b5e", 142 | "queueId":"Hiii!!!", 143 | "rankedLeagueDivision":"III", 144 | "rankedLeagueQueue":"RANKED_SOL0_5x5", 145 | "rankedLeagueTier":"SILVER", 146 | "rankedLosses'":"O", 147 | "rankedPrevSeasonDivision":"IV", 148 | "rankedPrevSeasonTier":"SILVER", 149 | "rankedSplitRewardLever":"0", 150 | "rankedWins":"38", 151 | "skinVariant":"91000", 152 | "skinname":"Talon", 153 | "timeStamp":"1646014091142" 154 | } 155 | 156 | await fs.writeFileSync(vulxConfigPath + "league.json", JSON.stringify(config)); 157 | this.leagueConfig = config; 158 | } 159 | 160 | async _createVulxConfig() { 161 | const config = { 162 | port: 80, 163 | discordRpc: false, 164 | experimental: false, 165 | firstLaunch: true, 166 | webTooltips: true 167 | } 168 | 169 | await fs.writeFileSync(vulxConfigPath + "vulx.json", JSON.stringify(config)); 170 | this.vulxConfig = config; 171 | } 172 | 173 | async _createExperimentsConfig() { 174 | const config = { 175 | league: false 176 | } 177 | 178 | await fs.writeFileSync(vulxConfigPath + "experiments.json", JSON.stringify(config)); 179 | this.experimentsConfig = config; 180 | } 181 | 182 | async _getValorantConfig() { 183 | const config = JSON.parse(fs.readFileSync(vulxConfigPath + "valorant.json")); 184 | this.valorantConfig = config; 185 | return config; 186 | } 187 | 188 | async _getLeagueConfig() { 189 | const config = JSON.parse(fs.readFileSync(vulxConfigPath + "league.json")); 190 | this.leagueConfig = config; 191 | return config; 192 | } 193 | 194 | async _getVulxConfig() { 195 | const config = JSON.parse(fs.readFileSync(vulxConfigPath + "vulx.json")); 196 | this.vulxConfig = config; 197 | return config; 198 | } 199 | 200 | async _getExperimentsConfig() { 201 | const config = JSON.parse(fs.readFileSync(vulxConfigPath + "experiments.json")); 202 | this.experimentsConfig = config; 203 | return config; 204 | } 205 | 206 | async getValorantConfig() { 207 | await this._initialize(); 208 | 209 | return await this._getValorantConfig(); 210 | } 211 | 212 | async getLeagueConfig() { 213 | await this._initialize(); 214 | 215 | return this.leagueConfig; 216 | } 217 | 218 | async getVulxConfig() { 219 | await this._initialize(); 220 | 221 | return await this._getVulxConfig(); 222 | } 223 | 224 | async getExperimentsConfig() { 225 | await this._initialize(); 226 | 227 | return this.experimentsConfig; 228 | } 229 | 230 | async resetConfig() { 231 | await this._createValorantConfig(); 232 | await this._createLeagueConfig(); 233 | await this._createVulxConfig(); 234 | await this._createExperimentsConfig(); 235 | } 236 | 237 | async saveConfig() { 238 | await fs.writeFileSync(vulxConfigPath + "valorant.json", JSON.stringify(this.valorantConfig)); 239 | await fs.writeFileSync(vulxConfigPath + "league.json", JSON.stringify(this.leagueConfig)); 240 | await fs.writeFileSync(vulxConfigPath + "vulx.json", JSON.stringify(this.vulxConfig)); 241 | await fs.writeFileSync(vulxConfigPath + "experiments.json", JSON.stringify(this.experimentsConfig)); 242 | } 243 | } 244 | 245 | module.exports = new Helper(); -------------------------------------------------------------------------------- /utils/discordHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const RPC = require("discord-rpc") 9 | const Logger = require('./Logger') 10 | const ConfigHelper = require('./ConfigHelper'); 11 | const meHelper = require("./meHelper"); 12 | 13 | const rankIdToName = { 14 | "-1": "Empty", 15 | 0: "Unranked", 16 | 1: "Unused 1", 17 | 2: "Unused 2", 18 | 3: "Iron 1", 19 | 4: "Iron 2", 20 | 5: "Iron 3", 21 | 6: "Bronze 1", 22 | 7: "Bronze 2", 23 | 8: "Bronze 3", 24 | 9: "Silver 1", 25 | 10: "Silver 2", 26 | 11: "Silver 3", 27 | 12: "Gold 1", 28 | 13: "Gold 2", 29 | 14: "Gold 3", 30 | 15: "Platinum 1", 31 | 16: "Platinum 2", 32 | 17: "Platinum 3", 33 | 18: "Diamond 1", 34 | 19: "Diamond 2", 35 | 20: "Diamond 3", 36 | 21: "Ascendant 1", 37 | 22: "Ascendant 2", 38 | 23: "Ascendant 3", 39 | 24: "Immortal 1", 40 | 25: "Immortal 2", 41 | 26: "Immortal 3", 42 | 27: "Radiant", 43 | } 44 | exports.rankIdToName = rankIdToName; 45 | 46 | const clientId = "948363491100721242"; 47 | const client = new RPC.Client({ transport: 'ipc' }); 48 | 49 | module.exports.refreshActivity = function() { 50 | try { 51 | if (!client) return; 52 | ConfigHelper.getVulxConfig().then(config => { 53 | ConfigHelper.getValorantConfig().then(valorantConfig => { 54 | if(config.discordRpc) { 55 | const activity = { 56 | details : "プロフィールエディタ", //Profile Editor (Japanese) 57 | state : `${valorantConfig.queueId.length < 128 ? valorantConfig.queueId : 'Playing Valorant' || 'Playing Valorant'}`, 58 | assets : { 59 | large_image : "logo", 60 | large_text : "Vulx", 61 | small_image: `${valorantConfig.competitiveTier < 3 ? 0 : valorantConfig.competitiveTier || 'logo2'}`, 62 | small_text: `${rankIdToName[valorantConfig.competitiveTier] || 'Cannot get rank.'}${valorantConfig.leaderboardPosition != 0 ? ` #${valorantConfig.leaderboardPosition}` : ''}`, 63 | }, 64 | buttons : [{label : "Discord" , url : "https://discord.gg/vulx"},{label : "YouTube" , url : "https://youtube.com/aqua"}] 65 | } 66 | client.request('SET_ACTIVITY', { 67 | pid: process.pid, 68 | activity, 69 | }).catch((err) => { Logger.debug(`Failed to update Discord RPC :: ${err.message}`) }) 70 | Logger.debug(`Updated Discord RPC :: ${JSON.stringify(activity)}`); 71 | } 72 | else { 73 | client.clearActivity(); 74 | } 75 | }) 76 | }) 77 | } catch (err) { 78 | Logger.debug(`Failed refresh Discord RPC activity :: ${err}`); 79 | } 80 | } 81 | 82 | module.exports.init = function() { 83 | client.login({ clientId }) 84 | .then(async () => { 85 | Logger.debug(`Initialised Discord RPC | Client ID: ${clientId}`); 86 | await this.refreshActivity(); 87 | }) 88 | .catch(err => { 89 | Logger.debug(`Failed to initialise Discord RPC :: ${err}`); 90 | }); 91 | } -------------------------------------------------------------------------------- /utils/jsonHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const ValorantAPI = require('./ValorantAPI'); 9 | 10 | module.exports.createJson = async function(settings, leagueToggle) { 11 | const lolSettingsEncoded = JSON.stringify(settings).toString() 12 | const config = Object.assign({}, settings); 13 | 14 | //await ValorantAPI.updatePlayerLoadout(config.accountLevel, config.playerCardId, config.playerTitleId) 15 | 16 | let status; 17 | if(config.partyId == "" || config.partyId == null) status = "offline"; 18 | else if(config.isValid == false) status = "dnd"; 19 | else if(config.sessionLoopState == "MENUS" && config.isIdle == true) status = "away"; 20 | else status = "chat"; 21 | 22 | config.partyClientVersion = await ValorantAPI.getClientVersion(); 23 | return { 24 | state: status, 25 | private: leagueToggle ? lolSettingsEncoded : Buffer.from(JSON.stringify(config)).toString('base64'), 26 | shared: { 27 | actor: "", 28 | details: "", 29 | location: "", 30 | product: leagueToggle ? "league_of_legends" : "valorant", 31 | time: new Date().valueOf() + 35000 //Extended timestamp to allow update bypass 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /utils/lockfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | var fs = require('fs'); 9 | const Logger = require('./Logger') 10 | 11 | class Helper { 12 | constructor() { 13 | this.port, this.password, this.protocol; 14 | this.LockfilePath = process.env.LOCALAPPDATA + '\\Riot Games\\Riot Client\\Config\\lockfile'; 15 | this.RetryAmount = 40; 16 | this.RetryTimeout = 2000; 17 | } 18 | 19 | async _sleep(ms) { 20 | return new Promise(resolve => setTimeout(resolve, ms)); 21 | } 22 | 23 | async _getLockfile() { 24 | if (!fs.existsSync(this.LockfilePath)) return false; 25 | 26 | const lockfile = fs.readFileSync(this.LockfilePath, { encoding:'utf8' }) 27 | .toString() 28 | .split(":"); 29 | this.port = lockfile[2]; 30 | this.password = lockfile[3]; 31 | this.protocol = lockfile[4]; 32 | 33 | return true; 34 | } 35 | 36 | async _initializeLockFile() { 37 | for (let i = 0; i < this.RetryAmount; i++) { 38 | if (await this._getLockfile()) break; 39 | Logger.debug('Failed to get lockfile, retrying..'); 40 | await this._sleep(this.RetryTimeout); 41 | } 42 | 43 | if (!this.port || !this.password || !this.protocol) { 44 | Logger.error('Failed to get lockfile, exiting..'); 45 | process.exit(); 46 | } 47 | 48 | Logger.info('Got lockfile!') 49 | 50 | return true; 51 | } 52 | 53 | async getLockfile() { 54 | if (!this.LockfileInitialized) { 55 | Logger.info('Grabbing lockfile..') 56 | this.LockfileInitialized = this._initializeLockFile();; 57 | } 58 | 59 | return this.LockfileInitialized; 60 | } 61 | } 62 | 63 | module.exports = new Helper(); -------------------------------------------------------------------------------- /utils/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const winston = require("winston"); 9 | require('winston-daily-rotate-file'); 10 | const os = require("os"); 11 | const fs = require("fs"); 12 | 13 | const isDevelopment = process.env.NODE_ENV == "development"; 14 | const hostname = os.hostname(); 15 | 16 | const vulxLogsPath = `${os.homedir()}/AppData/Local/ProjectX/logs/`; 17 | 18 | if (!fs.existsSync(vulxLogsPath)) { 19 | fs.mkdirSync(vulxLogsPath, { recursive: true }); 20 | } 21 | 22 | const Logger = winston.createLogger({ 23 | level: isDevelopment ? "debug" : "info", 24 | format: winston.format.json(), 25 | defaultMeta: { service: 'ProjectX' }, 26 | transports: [ 27 | new winston.transports.File({ filename: vulxLogsPath + "error.log", level: "error" }), 28 | new winston.transports.DailyRotateFile({ filename: vulxLogsPath + "combined-%DATE%.log", level: "debug", datePattern: "YYYY-MM-DD", maxFiles: "7d" }), 29 | new winston.transports.Console({ 30 | format: winston.format.combine( 31 | winston.format.timestamp(), 32 | winston.format.metadata({ 33 | fillExcept: ["timestamp", "service", "level", "message"], 34 | }), 35 | winston.format.colorize(), 36 | winstonConsoleFormat() 37 | ), 38 | }), 39 | ], 40 | }); 41 | 42 | 43 | function winstonConsoleFormat() { 44 | return winston.format.printf(({ timestamp, service, level, message }) => { 45 | return `[${timestamp}][${level}][${service}@${hostname}] ${message}`; 46 | }); 47 | } 48 | 49 | module.exports = Logger; -------------------------------------------------------------------------------- /utils/meHelper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Vulx - All Rights Reserved 3 | * Unauthorized copying of this file, via any medium is strictly prohibited 4 | * Proprietary and confidential 5 | * Written by Vulx Team , 2022 6 | */ 7 | 8 | const ConfigHelper = require('./ConfigHelper'); 9 | const { createJson } = require('./jsonHelper'); 10 | const AxiosHelper = require('./AxiosHelper'); 11 | const Logger = require('./Logger'); 12 | 13 | class Helper { 14 | constructor() { 15 | this.timer = null; 16 | this.leagueExperiment = false; 17 | } 18 | 19 | async _doInitialize() { 20 | await this._initializeTimer(); 21 | } 22 | 23 | async init() { 24 | if(!this.initializationPromise) { 25 | this.initializationPromise = this._doInitialize(); 26 | } 27 | return this.initializationPromise; 28 | } 29 | 30 | async _initializeTimer() { 31 | await this.emitMeRequest(); 32 | this.timer = setInterval(this.emitMeRequest.bind(this), 30000); 33 | } 34 | 35 | async _updateConfig(valorantConfig) { 36 | ConfigHelper.valorantConfig = valorantConfig; 37 | await ConfigHelper.saveConfig(); 38 | } 39 | 40 | async _updateConfigLeague(leagueConfig) { 41 | ConfigHelper.leagueConfig = leagueConfig; 42 | await ConfigHelper.saveConfig(); 43 | } 44 | 45 | async emitMeRequest() { 46 | const json = await createJson(await ConfigHelper.getValorantConfig(), this.leagueExperiment); 47 | if (!this.vulxAxios) this.vulxAxios = await AxiosHelper.getVulxAxios(); 48 | this.vulxAxios.put("/chat/v2/me", json) 49 | .then((res) => { 50 | if (!res.isAxiosError) { 51 | Logger.debug(`Successfully sent /me request to local Valorant API`) 52 | } 53 | }) 54 | .catch(() => Logger.info("Failed sending /me request to local Valorant API, has the game finished initializing?")); 55 | } 56 | 57 | async updateRequest(valorantConfig) { 58 | await this._updateConfig(valorantConfig); 59 | await this.emitMeRequest(); 60 | } 61 | 62 | async updateRequestLeague(leagueConfig) { 63 | await this._updateConfigLeague(leagueConfig); 64 | await this.emitMeRequest(); 65 | } 66 | 67 | async toggleLeagueExperiment() { 68 | this.leagueExperiment = !this.leagueExperiment; 69 | await this.emitMeRequest(); 70 | } 71 | } 72 | 73 | module.exports = new Helper(); -------------------------------------------------------------------------------- /valorant.json: -------------------------------------------------------------------------------- 1 | { 2 | "isValid":true, 3 | "sessionLoopState":"INGAME", 4 | "partyOwnerSessionLoopState":"INGAME", 5 | "customGameName":"", 6 | "customGameTeam":"", 7 | "partyOwnerMatchMap":"", 8 | "partyOwnerMatchCurrentTeam":"", 9 | "partyOwnerMatchScoreAllyTeam":"0", 10 | "partyOwnerMatchScoreEnemyTeam":"0", 11 | "partyOwnerProvisioningFlow":"Invalid", 12 | "provisioningFlow":"Invalid", 13 | "matchMap":"", 14 | "partyId":"58DsGJ20-9prT-7Jy8-h7hS-YXF1YXBsYXlz", 15 | "isPartyOwner":true, 16 | "partyState":"DEFAULT", 17 | "partyAccessibility":"CLOSED", 18 | "maxPartySize":5, 19 | "queueId":":)", 20 | "partyLFM":false, 21 | "partyClientVersion":"release-04.08-shipping-15-701907", 22 | "partySize":1, 23 | "tournamentId":"", 24 | "rosterId":"", 25 | "partyVersion":1650719279092, 26 | "queueEntryTime":"0001.01.01-00.00.00", 27 | "playerCardId":"30b64514-440d-1261-f863-6bbb180263f9", 28 | "playerTitleId":"00d4d326-4edc-3229-7c28-129d3374e3ad", 29 | "preferredLevelBorderId":"", 30 | "accountLevel":"200", 31 | "competitiveTier":"27", 32 | "leaderboardPosition":"432", 33 | "isIdle":true 34 | } -------------------------------------------------------------------------------- /vulx-vision.txt: -------------------------------------------------------------------------------- 1 | ---- This was the initial idea we used for creating Vulx ---- 2 | 3 | Project Description / Revision 4 | 5 | How will the frontend work? 6 | The application will create a web based interface to allow users to select and edit settings. 7 | Upon first launch the tab will open and function as followed. 8 | 9 | [+] The page will display a welcome message, centralised on the page 10 | [+] The page will ask some basic questions, for example: where is your valorant installed, have you downloaded the correct launcher ect. 11 | [+] After this a quick promt to say, Consider checking out the developer with some links (a button under showing, no thanks or skip) 12 | [+] This will take you to the main page, it will display some stats such as display name ect along with settings to edit every value of your profile. 13 | [+] Settings can be saved locally to allow quick loading. 14 | 15 | Display at the top Steps x/x (1/3) 16 | 17 | On initial launch (Local save will set a boolean and save the selected options + the settings they change): 18 | 19 | Ask if they'd like to use Discord RPC, 20 | Ask if they would like to use experimental features 21 | Promote the YouTube and Discord 22 | 23 | DiscordRPC Manager: 24 | 25 | [+] Display Vulx in the status, this will contain the current rank 26 | [+] It will also display the status selected if one is present. 27 | [+] Two buttons will be located display Discord & YouTube 28 | 29 | Note: This code will be easily accessible and will most likely be prone to skidding 30 | 31 | { 32 | "isValid": true, 33 | "sessionLoopState": "INGAME", //Player state: INGAME, MENUES (Making the state invalid / empty created purple profile) 34 | "partyOwnerSessionLoopState": "INGAME", //Player state: INGAME, MENUES (Making the state invalid / empty created purple profile) 35 | "customGameName": "", 36 | "customGameTeam": "", 37 | "partyOwnerMatchMap": "", //Map path used to display what map the player is in 38 | "partyOwnerMatchCurrentTeam": "", 39 | "partyOwnerMatchScoreAllyTeam": -1, //Score displayed on the left side of the user profile (Can use negative numbers) 40 | "partyOwnerMatchScoreEnemyTeam": -1, //Score displayed on the right side of the user profile (Can use negative numbers) 41 | "partyOwnerProvisioningFlow": "Invalid", 42 | "provisioningFlow": "Invalid", 43 | "matchMap": "", //Map path used to display what map the player is in 44 | "partyId": "58DsGJ20-9prT-7Jy8-h7hS-YXF1YXBsYXlz", 45 | "isPartyOwner": true, 46 | "partyState": "DEFAULT", 47 | "partyAccessibility": "CLOSED", //Allows players to join you (CLOSED, OPEN) 48 | "maxPartySize": 5, 49 | "queueId": "youtube.com/aqua <3", //This can be used for a custom status depending on the players state 50 | "partyLFM": false, 51 | "partyClientVersion": "release-04.07-shipping-15-699063", //Client version, Older versions will display red text on the profile 52 | "partySize": 1, //This number will display under the profile, EG: +789 53 | "tournamentId": "", 54 | "rosterId": "", 55 | "partyVersion": 1650719279092, 56 | "queueEntryTime": "0001.01.01-00.00.00", 57 | "playerCardId": "30b64514-440d-1261-f863-6bbb180263f9", //UUID Item for the banner under the player profile 58 | "playerTitleId": "00d4d326-4edc-3229-7c28-129d3374e3ad", //UUID Item for the title under the player profile 59 | "preferredLevelBorderId": "", 60 | "accountLevel": 720, //Users account level, this will only display for other users (Buggy / inconsistent) 61 | "competitiveTier": 24, //Rank displayed under the profile, EG: Diamond, Immortal, Radiant 62 | "leaderboardPosition": 1, //Position that will display next to the rank, EG: Radiant (#1) Note: 0 will remove the number 63 | "isIdle": true 64 | } --------------------------------------------------------------------------------