35 |
36 |
37 |
82 |
83 |
84 |
85 |
90 |
96 |
134 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
Vulx
149 |
150 |
151 |
152 |
153 | 🔹 :
154 | 🔹 :
155 | 🔹 :
156 |
157 |
158 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
243 |
244 |
245 |
246 |
252 |
253 |
Oops! You've found an unfinished feature.. Check back here in a few updates and maybe it will be populated!
254 |
255 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
281 |
282 |
283 |
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 |
38 | Lets Go!
39 |
40 |
41 |
42 |
43 |
44 |
VULX
45 | Valorant Profile Editor
46 |
47 |
48 |
Discord Presence
49 | Would you like to use our custom Discord status?
50 |
51 | Yes
52 |
53 |
54 | No
55 |
56 |
57 |
58 |
59 |
60 |
VULX
61 | Valorant Profile Editor
62 |
63 |
64 |
Test Features
65 | Would you like to test our experimental features?
66 |
67 | Yes
68 |
69 |
70 | No
71 |
72 |
73 |
74 |
75 |
76 |
VULX
77 | Valorant Profile Editor
78 |
79 |
80 |
Your ready to go!
81 | If you require support please join the Discord!
82 |
83 | Continue
84 |
85 |
86 | Discord
87 |
88 |
89 | YouTube
90 |
91 |
92 |
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 | }
--------------------------------------------------------------------------------