// ==UserScript== // @name Kxs Client - Survev.io Client // @namespace https://github.com/Kisakay/KxsClient // @version 2.0.6 // @description A client to enhance the survev.io in-game experience with many features, as well as future features. // @author Kisakay // @license AGPL-3.0 // @run-at document-end // @icon https://kxs.rip/assets/KysClientLogo.png // @match *://survev.io/* // @match *://66.179.254.36/* // @match *://zurviv.io/* // @match *://expandedwater.online/* // @match *://localhost:3000/* // @match *://surviv.wf/* // @match *://resurviv.biz/* // @match *://82.67.125.203/* // @match *://leia-uwu.github.io/survev/* // @match *://50v50.online/* // @match *://eu-comp.net/* // @match *://survev.leia-is.gay/* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/531396/Kxs%20Client%20-%20Survevio%20Client.user.js // @updateURL https://update.greasyfork.icu/scripts/531396/Kxs%20Client%20-%20Survevio%20Client.meta.js // ==/UserScript== ; /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ 123: /***/ ((module) => { const numeric = /^[0-9]+$/ const compareIdentifiers = (a, b) => { const anum = numeric.test(a) const bnum = numeric.test(b) if (anum && bnum) { a = +a b = +b } return a === b ? 0 : (anum && !bnum) ? -1 : (bnum && !anum) ? 1 : a < b ? -1 : 1 } const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) module.exports = { compareIdentifiers, rcompareIdentifiers, } /***/ }), /***/ 272: /***/ ((module) => { const debug = ( typeof process === 'object' && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ) ? (...args) => console.error('SEMVER', ...args) : () => {} module.exports = debug /***/ }), /***/ 560: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const SemVer = __webpack_require__(908) const compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose)) module.exports = compare /***/ }), /***/ 580: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const compare = __webpack_require__(560) const gt = (a, b, loose) => compare(a, b, loose) > 0 module.exports = gt /***/ }), /***/ 587: /***/ ((module) => { // parse out just the options we care about const looseOption = Object.freeze({ loose: true }) const emptyOpts = Object.freeze({ }) const parseOptions = options => { if (!options) { return emptyOpts } if (typeof options !== 'object') { return looseOption } return options } module.exports = parseOptions /***/ }), /***/ 718: /***/ ((module, exports, __webpack_require__) => { const { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH, } = __webpack_require__(874) const debug = __webpack_require__(272) exports = module.exports = {} // The actual regexps go on exports.re const re = exports.re = [] const safeRe = exports.safeRe = [] const src = exports.src = [] const safeSrc = exports.safeSrc = [] const t = exports.t = {} let R = 0 const LETTERDASHNUMBER = '[a-zA-Z0-9-]' // Replace some greedy regex tokens to prevent regex dos issues. These regex are // used internally via the safeRe object since all inputs in this library get // normalized first to trim and collapse all extra whitespace. The original // regexes are exported for userland consumption and lower level usage. A // future breaking change could export the safer regex only with a note that // all input should have extra whitespace removed. const safeRegexReplacements = [ ['\\s', 1], ['\\d', MAX_LENGTH], [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], ] const makeSafeRegex = (value) => { for (const [token, max] of safeRegexReplacements) { value = value .split(`${token}*`).join(`${token}{0,${max}}`) .split(`${token}+`).join(`${token}{1,${max}}`) } return value } const createToken = (name, value, isGlobal) => { const safe = makeSafeRegex(value) const index = R++ debug(name, index, value) t[name] = index src[index] = value safeSrc[index] = safe re[index] = new RegExp(value, isGlobal ? 'g' : undefined) safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) } // The following Regular Expressions can be used for tokenizing, // validating, and parsing SemVer version strings. // ## Numeric Identifier // A single `0`, or a non-zero digit followed by zero or more digits. createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') createToken('NUMERICIDENTIFIERLOOSE', '\\d+') // ## Non-numeric Identifier // Zero or more digits, followed by a letter or hyphen, and then zero or // more letters, digits, or hyphens. createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) // ## Main Version // Three dot-separated numeric identifiers. createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})`) createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})`) // ## Pre-release Version Identifier // A numeric identifier, or a non-numeric identifier. createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] }|${src[t.NONNUMERICIDENTIFIER]})`) createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] }|${src[t.NONNUMERICIDENTIFIER]})`) // ## Pre-release Version // Hyphen, followed by one or more dot-separated pre-release version // identifiers. createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] }(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] }(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) // ## Build Metadata Identifier // Any combination of digits, letters, or hyphens. createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) // ## Build Metadata // Plus sign, followed by one or more period-separated build metadata // identifiers. createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] }(?:\\.${src[t.BUILDIDENTIFIER]})*))`) // ## Full Version String // A main version, followed optionally by a pre-release version and // build metadata. // Note that the only major, minor, patch, and pre-release sections of // the version string are capturing groups. The build metadata is not a // capturing group, because it should not ever be used in version // comparison. createToken('FULLPLAIN', `v?${src[t.MAINVERSION] }${src[t.PRERELEASE]}?${ src[t.BUILD]}?`) createToken('FULL', `^${src[t.FULLPLAIN]}$`) // like full, but allows v1.2.3 and =1.2.3, which people do sometimes. // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty // common in the npm registry. createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] }${src[t.PRERELEASELOOSE]}?${ src[t.BUILD]}?`) createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) createToken('GTLT', '((?:<|>)?=?)') // Something like "2.*" or "1.2.x". // Note that "x.x" is a valid xRange identifer, meaning "any version" // Only the first item is strictly required. createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:${src[t.PRERELEASE]})?${ src[t.BUILD]}?` + `)?)?`) createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:${src[t.PRERELEASELOOSE]})?${ src[t.BUILD]}?` + `)?)?`) createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) // Coercion. // Extract anything that could conceivably be a part of a valid semver createToken('COERCEPLAIN', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`) createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`) createToken('COERCEFULL', src[t.COERCEPLAIN] + `(?:${src[t.PRERELEASE]})?` + `(?:${src[t.BUILD]})?` + `(?:$|[^\\d])`) createToken('COERCERTL', src[t.COERCE], true) createToken('COERCERTLFULL', src[t.COERCEFULL], true) // Tilde ranges. // Meaning is "reasonably at or greater than" createToken('LONETILDE', '(?:~>?)') createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) exports.tildeTrimReplace = '$1~' createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) // Caret ranges. // Meaning is "at least and backwards compatible with" createToken('LONECARET', '(?:\\^)') createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) exports.caretTrimReplace = '$1^' createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) // A simple gt/lt/eq thing, or just "" to indicate "any version" createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) // An expression to strip any whitespace between the gtlt and the thing // it modifies, so that `> 1.2.3` ==> `>1.2.3` createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] }\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) exports.comparatorTrimReplace = '$1$2$3' // Something like `1.2.3 - 1.2.4` // Note that these all use the loose form, because they'll be // checked against either the strict or loose comparator form // later. createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAIN]})` + `\\s*$`) createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAINLOOSE]})` + `\\s*$`) // Star ranges basically just allow anything at all. createToken('STAR', '(<|>)?=?\\s*\\*') // >=0.0.0 is like a star createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$') createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') /***/ }), /***/ 746: /***/ (() => { "use strict"; // --- HOOK GLOBAL WEBSOCKET POUR INTERCEPTION gameId & PTC monitoring --- (function () { const OriginalWebSocket = window.WebSocket; function HookedWebSocket(url, protocols) { const ws = protocols !== undefined ? new OriginalWebSocket(url, protocols) : new OriginalWebSocket(url); if (typeof url === "string" && url.includes("gameId=")) { const gameId = url.split("gameId=")[1]; globalThis.kxsClient.kxsNetwork.sendGameInfoToWebSocket(gameId); } return ws; } // Copie le prototype HookedWebSocket.prototype = OriginalWebSocket.prototype; // Copie les propriétés statiques (CONNECTING, OPEN, etc.) Object.defineProperties(HookedWebSocket, { CONNECTING: { value: OriginalWebSocket.CONNECTING, writable: false }, OPEN: { value: OriginalWebSocket.OPEN, writable: false }, CLOSING: { value: OriginalWebSocket.CLOSING, writable: false }, CLOSED: { value: OriginalWebSocket.CLOSED, writable: false }, }); // Remplace le constructeur global window.WebSocket = HookedWebSocket; })(); /***/ }), /***/ 814: /***/ ((__unused_webpack_module, exports) => { "use strict"; var __webpack_unused_export__; __webpack_unused_export__ = ({ value: true }); exports.w = void 0; ; class SteganoDB { data; currentTable; options; database; constructor(options) { this.currentTable = options?.tableName || "json"; this.database = options.database || "stegano.db"; this.data = { [this.currentTable]: [] }; this.fetchDataFromFile(); } read() { return localStorage.getItem(this.database) || this.data; } write() { return localStorage.setItem(this.database, JSON.stringify(this.data)); } setNestedProperty = (object, key, value) => { const properties = key.split('.'); let currentObject = object; for (let i = 0; i < properties.length - 1; i++) { const property = properties[i]; if (typeof currentObject[property] !== 'object' || currentObject[property] === null) { currentObject[property] = {}; } currentObject = currentObject[property]; } currentObject[properties[properties.length - 1]] = value; }; getNestedProperty = (object, key) => { const properties = key.split('.'); let index = 0; for (; index < properties.length; ++index) { object = object && object[properties[index]]; } return object; }; fetchDataFromFile() { try { const content = this.read(); this.data = JSON.parse(content); } catch (error) { this.data = { [this.currentTable]: [] }; } } updateNestedProperty(key, operation, value) { const [id, ...rest] = key.split('.'); const nestedPath = rest.join('.'); let currentValue = this.data[this.currentTable].find((entry) => entry.id === id); if (!currentValue && operation !== 'get') { currentValue = { id, value: {} }; this.data[this.currentTable].push(currentValue); } if (!currentValue && operation === 'get') { return undefined; } switch (operation) { case 'get': return nestedPath ? this.getNestedProperty(currentValue.value, nestedPath) : currentValue.value; case 'set': if (nestedPath) { this.setNestedProperty(currentValue.value, nestedPath, value); } else { currentValue.value = value; } this.write(); break; case 'add': if (!nestedPath) { currentValue.value = (typeof currentValue.value === 'number' ? currentValue.value : 0) + value; } else { const existingValue = this.getNestedProperty(currentValue.value, nestedPath); if (typeof existingValue !== 'number' && existingValue !== undefined) { throw new TypeError('The existing value is not a number.'); } this.setNestedProperty(currentValue.value, nestedPath, (typeof existingValue === 'number' ? existingValue : 0) + value); } this.write(); break; case 'sub': if (!nestedPath) { currentValue.value = (typeof currentValue.value === 'number' ? currentValue.value : 0) - value; } else { const existingValue = this.getNestedProperty(currentValue.value, nestedPath); if (typeof existingValue !== 'number' && existingValue !== undefined && existingValue !== null) { throw new TypeError('The existing value is not a number.'); } this.setNestedProperty(currentValue.value, nestedPath, (typeof existingValue === 'number' ? existingValue : 0) - value); } this.write(); break; case 'delete': if (nestedPath) { const properties = nestedPath.split('.'); let currentObject = currentValue.value; for (let i = 0; i < properties.length - 1; i++) { const property = properties[i]; if (!currentObject[property]) { return; } currentObject = currentObject[property]; } delete currentObject[properties[properties.length - 1]]; } else { const index = this.data[this.currentTable].findIndex((entry) => entry.id === id); if (index !== -1) { this.data[this.currentTable].splice(index, 1); } } this.write(); break; case 'pull': const existingArray = nestedPath ? this.getNestedProperty(currentValue.value, nestedPath) : currentValue.value; if (!Array.isArray(existingArray)) { throw new Error('The stored value is not an array'); } const newArray = existingArray.filter((item) => item !== value); if (nestedPath) { this.setNestedProperty(currentValue.value, nestedPath, newArray); } else { currentValue.value = newArray; } this.write(); break; } } table(tableName) { if (tableName.includes(" ") || !tableName || tableName === "") { throw new SyntaxError("Key can't be null or contain a space."); } if (!this.data[tableName]) { this.data[tableName] = []; } return new SteganoDB(this.options); } get(key) { return this.updateNestedProperty(key, 'get'); } set(key, value) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null or contain a space."); } this.updateNestedProperty(key, 'set', value); } pull(key, value) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null or contain a space."); } this.updateNestedProperty(key, 'pull', value); } add(key, count) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null or contain a space."); } if (isNaN(count)) { throw new SyntaxError("The value is NaN."); } this.updateNestedProperty(key, 'add', count); } sub(key, count) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null or contain a space."); } if (isNaN(count)) { throw new SyntaxError("The value is NaN."); } this.updateNestedProperty(key, 'sub', count); } delete(key) { this.updateNestedProperty(key, 'delete'); } cache(key, value, time) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null ou contain a space."); } if (!time || isNaN(time)) { throw new SyntaxError("The time needs to be a number. (ms)"); } this.updateNestedProperty(key, 'set', value); setTimeout(() => { this.updateNestedProperty(key, 'delete'); }, time); } push(key, element) { if (key.includes(" ") || !key || key === "") { throw new SyntaxError("Key can't be null or contain a space."); } const [id, ...rest] = key.split('.'); const nestedPath = rest.join('.'); let currentValue = this.data[this.currentTable].find((entry) => entry.id === id); if (!currentValue) { currentValue = { id, value: nestedPath ? {} : [] }; this.data[this.currentTable].push(currentValue); } if (nestedPath) { const existingArray = this.getNestedProperty(currentValue.value, nestedPath); if (!existingArray) { this.setNestedProperty(currentValue.value, nestedPath, [element]); } else if (!Array.isArray(existingArray)) { throw new Error('The stored value is not an array'); } else { existingArray.push(element); this.setNestedProperty(currentValue.value, nestedPath, existingArray); } } else { if (!Array.isArray(currentValue.value)) { currentValue.value = []; } currentValue.value.push(element); } this.write(); } has(key) { return Boolean(this.get(key)); } deleteAll() { this.data[this.currentTable] = []; this.write(); } all() { return this.data[this.currentTable]; } } exports.w = SteganoDB; /***/ }), /***/ 874: /***/ ((module) => { // Note: this is the semver.org version of the spec that it implements // Not necessarily the package version of this code. const SEMVER_SPEC_VERSION = '2.0.0' const MAX_LENGTH = 256 const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || /* istanbul ignore next */ 9007199254740991 // Max safe segment length for coercion. const MAX_SAFE_COMPONENT_LENGTH = 16 // Max safe length for a build identifier. The max length minus 6 characters for // the shortest version with a build 0.0.0+BUILD. const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 const RELEASE_TYPES = [ 'major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease', ] module.exports = { MAX_LENGTH, MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_SAFE_INTEGER, RELEASE_TYPES, SEMVER_SPEC_VERSION, FLAG_INCLUDE_PRERELEASE: 0b001, FLAG_LOOSE: 0b010, } /***/ }), /***/ 908: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const debug = __webpack_require__(272) const { MAX_LENGTH, MAX_SAFE_INTEGER } = __webpack_require__(874) const { safeRe: re, safeSrc: src, t } = __webpack_require__(718) const parseOptions = __webpack_require__(587) const { compareIdentifiers } = __webpack_require__(123) class SemVer { constructor (version, options) { options = parseOptions(options) if (version instanceof SemVer) { if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) { return version } else { version = version.version } } else if (typeof version !== 'string') { throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`) } if (version.length > MAX_LENGTH) { throw new TypeError( `version is longer than ${MAX_LENGTH} characters` ) } debug('SemVer', version, options) this.options = options this.loose = !!options.loose // this isn't actually relevant for versions, but keep it so that we // don't run into trouble passing this.options around. this.includePrerelease = !!options.includePrerelease const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) if (!m) { throw new TypeError(`Invalid Version: ${version}`) } this.raw = version // these are actually numbers this.major = +m[1] this.minor = +m[2] this.patch = +m[3] if (this.major > MAX_SAFE_INTEGER || this.major < 0) { throw new TypeError('Invalid major version') } if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { throw new TypeError('Invalid minor version') } if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { throw new TypeError('Invalid patch version') } // numberify any prerelease numeric ids if (!m[4]) { this.prerelease = [] } else { this.prerelease = m[4].split('.').map((id) => { if (/^[0-9]+$/.test(id)) { const num = +id if (num >= 0 && num < MAX_SAFE_INTEGER) { return num } } return id }) } this.build = m[5] ? m[5].split('.') : [] this.format() } format () { this.version = `${this.major}.${this.minor}.${this.patch}` if (this.prerelease.length) { this.version += `-${this.prerelease.join('.')}` } return this.version } toString () { return this.version } compare (other) { debug('SemVer.compare', this.version, this.options, other) if (!(other instanceof SemVer)) { if (typeof other === 'string' && other === this.version) { return 0 } other = new SemVer(other, this.options) } if (other.version === this.version) { return 0 } return this.compareMain(other) || this.comparePre(other) } compareMain (other) { if (!(other instanceof SemVer)) { other = new SemVer(other, this.options) } return ( compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch) ) } comparePre (other) { if (!(other instanceof SemVer)) { other = new SemVer(other, this.options) } // NOT having a prerelease is > having one if (this.prerelease.length && !other.prerelease.length) { return -1 } else if (!this.prerelease.length && other.prerelease.length) { return 1 } else if (!this.prerelease.length && !other.prerelease.length) { return 0 } let i = 0 do { const a = this.prerelease[i] const b = other.prerelease[i] debug('prerelease compare', i, a, b) if (a === undefined && b === undefined) { return 0 } else if (b === undefined) { return 1 } else if (a === undefined) { return -1 } else if (a === b) { continue } else { return compareIdentifiers(a, b) } } while (++i) } compareBuild (other) { if (!(other instanceof SemVer)) { other = new SemVer(other, this.options) } let i = 0 do { const a = this.build[i] const b = other.build[i] debug('build compare', i, a, b) if (a === undefined && b === undefined) { return 0 } else if (b === undefined) { return 1 } else if (a === undefined) { return -1 } else if (a === b) { continue } else { return compareIdentifiers(a, b) } } while (++i) } // preminor will bump the version up to the next minor release, and immediately // down to pre-release. premajor and prepatch work the same way. inc (release, identifier, identifierBase) { if (release.startsWith('pre')) { if (!identifier && identifierBase === false) { throw new Error('invalid increment argument: identifier is empty') } // Avoid an invalid semver results if (identifier) { const r = new RegExp(`^${this.options.loose ? src[t.PRERELEASELOOSE] : src[t.PRERELEASE]}$`) const match = `-${identifier}`.match(r) if (!match || match[1] !== identifier) { throw new Error(`invalid identifier: ${identifier}`) } } } switch (release) { case 'premajor': this.prerelease.length = 0 this.patch = 0 this.minor = 0 this.major++ this.inc('pre', identifier, identifierBase) break case 'preminor': this.prerelease.length = 0 this.patch = 0 this.minor++ this.inc('pre', identifier, identifierBase) break case 'prepatch': // If this is already a prerelease, it will bump to the next version // drop any prereleases that might already exist, since they are not // relevant at this point. this.prerelease.length = 0 this.inc('patch', identifier, identifierBase) this.inc('pre', identifier, identifierBase) break // If the input is a non-prerelease version, this acts the same as // prepatch. case 'prerelease': if (this.prerelease.length === 0) { this.inc('patch', identifier, identifierBase) } this.inc('pre', identifier, identifierBase) break case 'release': if (this.prerelease.length === 0) { throw new Error(`version ${this.raw} is not a prerelease`) } this.prerelease.length = 0 break case 'major': // If this is a pre-major version, bump up to the same major version. // Otherwise increment major. // 1.0.0-5 bumps to 1.0.0 // 1.1.0 bumps to 2.0.0 if ( this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0 ) { this.major++ } this.minor = 0 this.patch = 0 this.prerelease = [] break case 'minor': // If this is a pre-minor version, bump up to the same minor version. // Otherwise increment minor. // 1.2.0-5 bumps to 1.2.0 // 1.2.1 bumps to 1.3.0 if (this.patch !== 0 || this.prerelease.length === 0) { this.minor++ } this.patch = 0 this.prerelease = [] break case 'patch': // If this is not a pre-release version, it will increment the patch. // If it is a pre-release it will bump up to the same patch version. // 1.2.0-5 patches to 1.2.0 // 1.2.0 patches to 1.2.1 if (this.prerelease.length === 0) { this.patch++ } this.prerelease = [] break // This probably shouldn't be used publicly. // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. case 'pre': { const base = Number(identifierBase) ? 1 : 0 if (this.prerelease.length === 0) { this.prerelease = [base] } else { let i = this.prerelease.length while (--i >= 0) { if (typeof this.prerelease[i] === 'number') { this.prerelease[i]++ i = -2 } } if (i === -1) { // didn't increment anything if (identifier === this.prerelease.join('.') && identifierBase === false) { throw new Error('invalid increment argument: identifier already exists') } this.prerelease.push(base) } } if (identifier) { // 1.2.0-beta.1 bumps to 1.2.0-beta.2, // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 let prerelease = [identifier, base] if (identifierBase === false) { prerelease = [identifier] } if (compareIdentifiers(this.prerelease[0], identifier) === 0) { if (isNaN(this.prerelease[1])) { this.prerelease = prerelease } } else { this.prerelease = prerelease } } break } default: throw new Error(`invalid increment argument: ${release}`) } this.raw = this.format() if (this.build.length) { this.raw += `+${this.build.join('.')}` } return this } } module.exports = SemVer /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // This entry needs to be wrapped in an IIFE because it needs to be in strict mode. (() => { "use strict"; // EXTERNAL MODULE: ./src/UTILS/websocket-hook.ts var websocket_hook = __webpack_require__(746); ;// ./config.json const config_namespaceObject = /*#__PURE__*/JSON.parse('{"base_url":"https://kxs.rip","api_url":"https://network.kxs.rip","fileName":"KxsClient.user.js","match":["*://survev.io/*","*://66.179.254.36/*","*://zurviv.io/*","*://expandedwater.online/*","*://localhost:3000/*","*://surviv.wf/*","*://resurviv.biz/*","*://82.67.125.203/*","*://leia-uwu.github.io/survev/*","*://50v50.online/*","*://eu-comp.net/*","*://survev.leia-is.gay/*"],"grant":["none"]}'); ;// ./src/UTILS/vars.ts const background_song = config_namespaceObject.base_url + "/assets/Stranger_Things_Theme_Song_C418_REMIX.mp3"; const kxs_logo = config_namespaceObject.base_url + "/assets/KysClientLogo.png"; const full_logo = config_namespaceObject.base_url + "/assets/KysClient.gif"; const background_image = config_namespaceObject.base_url + "/assets/background.jpg"; const win_sound = config_namespaceObject.base_url + "/assets/win.m4a"; const death_sound = config_namespaceObject.base_url + "/assets/dead.m4a"; ;// ./src/MECHANIC/intercept.ts function intercept(link, targetUrl) { const open = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url) { if (url.includes(link)) { arguments[1] = targetUrl; } open.apply(this, arguments); }; const originalFetch = window.fetch; window.fetch = function (url, options) { if (url.includes(link)) { url = targetUrl; } return originalFetch.apply(this, arguments); }; } ;// ./src/HUD/MOD/HealthWarning.ts class HealthWarning { constructor(kxsClient) { this.isDraggable = false; this.isDragging = false; this.dragOffset = { x: 0, y: 0 }; this.POSITION_KEY = 'lowHpWarning'; this.menuCheckInterval = null; this.warningElement = null; this.kxsClient = kxsClient; this.createWarningElement(); this.setFixedPosition(); this.setupDragAndDrop(); this.startMenuCheckInterval(); } createWarningElement() { const warning = document.createElement("div"); const uiTopLeft = document.getElementById("ui-top-left"); warning.style.cssText = ` position: fixed; background: rgba(0, 0, 0, 0.8); border: 2px solid #ff0000; border-radius: 5px; padding: 10px 15px; color: #ff0000; font-family: Arial, sans-serif; font-size: 14px; z-index: 9999; display: none; backdrop-filter: blur(5px); pointer-events: none; transition: border-color 0.3s ease; `; const content = document.createElement("div"); content.style.cssText = ` display: flex; align-items: center; gap: 8px; `; const icon = document.createElement("div"); icon.innerHTML = ` `; const text = document.createElement("span"); text.textContent = "LOW HP!"; if (uiTopLeft) { content.appendChild(icon); content.appendChild(text); warning.appendChild(content); uiTopLeft.appendChild(warning); } this.warningElement = warning; this.addPulseAnimation(); } setFixedPosition() { if (!this.warningElement) return; // Récupérer la position depuis le localStorage ou les valeurs par défaut const storageKey = `position_${this.POSITION_KEY}`; const savedPosition = localStorage.getItem(storageKey); let position; if (savedPosition) { try { // Utiliser la position sauvegardée const { x, y } = JSON.parse(savedPosition); position = { left: x, top: y }; } catch (error) { // En cas d'erreur, utiliser la position par défaut position = this.kxsClient.defaultPositions[this.POSITION_KEY]; this.kxsClient.logger.error('Erreur lors du chargement de la position LOW HP:', error); } } else { // Utiliser la position par défaut position = this.kxsClient.defaultPositions[this.POSITION_KEY]; } // Appliquer la position if (position) { this.warningElement.style.top = `${position.top}px`; this.warningElement.style.left = `${position.left}px`; } } addPulseAnimation() { const keyframes = ` @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } `; const style = document.createElement("style"); style.textContent = keyframes; document.head.appendChild(style); if (this.warningElement) { this.warningElement.style.animation = "pulse 1.5s infinite"; } } show(health) { if (!this.warningElement) return; this.warningElement.style.display = "block"; const span = this.warningElement.querySelector("span"); if (span) { span.textContent = `LOW HP: ${health}%`; } } hide() { if (!this.warningElement) return; // Ne pas masquer si en mode placement // if (this.isDraggable) return; this.warningElement.style.display = "none"; } update(health) { // Si le mode placement est actif (isDraggable), on ne fait rien pour maintenir l'affichage if (this.isDraggable) { return; } // Sinon, comportement normal if (health <= 30 && health > 0) { this.show(health); } else { this.hide(); } } setupDragAndDrop() { // Nous n'avons plus besoin d'écouteurs pour RSHIFT car nous utilisons maintenant // l'état du menu secondaire pour déterminer quand activer/désactiver le mode placement // Écouteurs d'événements de souris pour le glisser-déposer document.addEventListener('mousedown', this.handleMouseDown.bind(this)); document.addEventListener('mousemove', this.handleMouseMove.bind(this)); document.addEventListener('mouseup', this.handleMouseUp.bind(this)); } enableDragging() { if (!this.warningElement) return; this.isDraggable = true; this.warningElement.style.pointerEvents = 'auto'; this.warningElement.style.cursor = 'move'; this.warningElement.style.borderColor = '#00ff00'; // Feedback visuel quand déplaçable // Force l'affichage de l'avertissement LOW HP, peu importe la santé actuelle this.warningElement.style.display = 'block'; const span = this.warningElement.querySelector("span"); if (span) { span.textContent = 'LOW HP: Placement Mode'; } } disableDragging() { if (!this.warningElement) return; this.isDraggable = false; this.isDragging = false; this.warningElement.style.pointerEvents = 'none'; this.warningElement.style.cursor = 'default'; this.warningElement.style.borderColor = '#ff0000'; // Retour à la couleur normale // Remet le texte original si l'avertissement est visible if (this.warningElement.style.display === 'block') { const span = this.warningElement.querySelector("span"); if (span) { span.textContent = 'LOW HP'; } } // Récupérer la santé actuelle à partir de l'élément UI de santé du jeu const healthBars = document.querySelectorAll("#ui-health-container"); if (healthBars.length > 0) { const bar = healthBars[0].querySelector("#ui-health-actual"); if (bar) { const currentHealth = Math.round(parseFloat(bar.style.width)); // Forcer une mise à jour immédiate en fonction de la santé actuelle this.update(currentHealth); } } } handleMouseDown(event) { if (!this.isDraggable || !this.warningElement) return; // Check if click was on the warning element if (this.warningElement.contains(event.target)) { this.isDragging = true; // Calculate offset from mouse position to element corner const rect = this.warningElement.getBoundingClientRect(); this.dragOffset = { x: event.clientX - rect.left, y: event.clientY - rect.top }; // Prevent text selection during drag event.preventDefault(); } } handleMouseMove(event) { if (!this.isDragging || !this.warningElement) return; // Calculate new position const newX = event.clientX - this.dragOffset.x; const newY = event.clientY - this.dragOffset.y; // Update element position this.warningElement.style.left = `${newX}px`; this.warningElement.style.top = `${newY}px`; } handleMouseUp() { if (this.isDragging && this.warningElement) { this.isDragging = false; // Récupérer les positions actuelles const left = parseInt(this.warningElement.style.left); const top = parseInt(this.warningElement.style.top); // Sauvegarder la position const storageKey = `position_${this.POSITION_KEY}`; localStorage.setItem(storageKey, JSON.stringify({ x: left, y: top })); } } startMenuCheckInterval() { // Créer un intervalle qui vérifie régulièrement l'état du menu RSHIFT this.menuCheckInterval = window.setInterval(() => { var _a; // Vérifier si le menu secondaire est ouvert const isMenuOpen = ((_a = this.kxsClient.secondaryMenu) === null || _a === void 0 ? void 0 : _a.isOpen) || false; // Si le menu est ouvert et que nous ne sommes pas en mode placement, activer le mode placement if (isMenuOpen && this.kxsClient.isHealthWarningEnabled && !this.isDraggable) { this.enableDragging(); } // Si le menu est fermé et que nous sommes en mode placement, désactiver le mode placement else if (!isMenuOpen && this.isDraggable) { this.disableDragging(); } }, 100); // Vérifier toutes les 100ms } } ;// ./src/MECHANIC/KillLeaderTracking.ts class KillLeaderTracker { constructor(kxsClient) { this.offsetX = 20; this.offsetY = 20; this.lastKnownKills = 0; this.wasKillLeader = false; this.MINIMUM_KILLS_FOR_LEADER = 3; this.kxsClient = kxsClient; this.warningElement = null; this.encouragementElement = null; this.killLeaderKillCount = 0; this.wasKillLeader = false; this.createEncouragementElement(); this.initMouseTracking(); } createEncouragementElement() { const encouragement = document.createElement("div"); encouragement.style.cssText = ` position: fixed; background: rgba(0, 255, 0, 0.1); border: 2px solid #00ff00; border-radius: 5px; padding: 10px 15px; color: #00ff00; font-family: Arial, sans-serif; font-size: 14px; z-index: 9999; display: none; backdrop-filter: blur(5px); transition: all 0.3s ease; pointer-events: none; box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); `; const content = document.createElement("div"); content.style.cssText = ` display: flex; align-items: center; gap: 8px; `; const icon = document.createElement("div"); icon.innerHTML = ` `; const text = document.createElement("span"); text.textContent = "Nice Kill!"; content.appendChild(icon); content.appendChild(text); encouragement.appendChild(content); document.body.appendChild(encouragement); this.encouragementElement = encouragement; this.addEncouragementAnimation(); } initMouseTracking() { document.addEventListener("mousemove", (e) => { this.updateElementPosition(this.warningElement, e); this.updateElementPosition(this.encouragementElement, e); }); } updateElementPosition(element, e) { if (!element || element.style.display === "none") return; const x = e.clientX + this.offsetX; const y = e.clientY + this.offsetY; const rect = element.getBoundingClientRect(); const maxX = window.innerWidth - rect.width; const maxY = window.innerHeight - rect.height; const finalX = Math.min(Math.max(0, x), maxX); const finalY = Math.min(Math.max(0, y), maxY); element.style.transform = `translate(${finalX}px, ${finalY}px)`; } addEncouragementAnimation() { const keyframes = ` @keyframes encouragementPulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.8; } 100% { transform: scale(1); opacity: 1; } } @keyframes fadeInOut { 0% { opacity: 0; transform: translateY(20px); } 10% { opacity: 1; transform: translateY(0); } 90% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-20px); } } `; const style = document.createElement("style"); style.textContent = keyframes; document.head.appendChild(style); if (this.encouragementElement) { this.encouragementElement.style.animation = "fadeInOut 3s forwards"; } } showEncouragement(killsToLeader, isDethrone = false, noKillLeader = false) { if (!this.encouragementElement) return; let message; if (isDethrone && killsToLeader !== 0) { message = "Oh no! You've been dethroned!"; this.encouragementElement.style.borderColor = "#ff0000"; this.encouragementElement.style.color = "#ff0000"; this.encouragementElement.style.background = "rgba(255, 0, 0, 0.1)"; } else if (noKillLeader) { const killsNeeded = this.MINIMUM_KILLS_FOR_LEADER - this.lastKnownKills; message = `Nice Kill! Get ${killsNeeded} more kills to become the first Kill Leader!`; } else { message = killsToLeader <= 0 ? "You're the Kill Leader! 👑" : `Nice Kill! ${killsToLeader} more to become Kill Leader!`; } const span = this.encouragementElement.querySelector("span"); if (span) span.textContent = message; this.encouragementElement.style.display = "block"; this.encouragementElement.style.animation = "fadeInOut 3s forwards"; setTimeout(() => { if (this.encouragementElement) { this.encouragementElement.style.display = "none"; // Reset colors this.encouragementElement.style.borderColor = "#00ff00"; this.encouragementElement.style.color = "#00ff00"; this.encouragementElement.style.background = "rgba(0, 255, 0, 0.1)"; } }, 7000); } isKillLeader() { const killLeaderNameElement = document.querySelector("#ui-kill-leader-name"); return this.kxsClient.getPlayerName() === (killLeaderNameElement === null || killLeaderNameElement === void 0 ? void 0 : killLeaderNameElement.textContent); } update(myKills) { if (!this.kxsClient.isKillLeaderTrackerEnabled) return; const killLeaderElement = document.querySelector("#ui-kill-leader-count"); this.killLeaderKillCount = parseInt((killLeaderElement === null || killLeaderElement === void 0 ? void 0 : killLeaderElement.textContent) || "0", 10); if (myKills > this.lastKnownKills) { if (this.killLeaderKillCount === 0) { // Pas encore de kill leader, encourager le joueur à atteindre 3 kills this.showEncouragement(0, false, true); } else if (this.killLeaderKillCount < this.MINIMUM_KILLS_FOR_LEADER) { // Ne rien faire si le kill leader n'a pas atteint le minimum requis return; } else if (this.isKillLeader()) { this.showEncouragement(0); this.wasKillLeader = true; } else { const killsNeeded = this.killLeaderKillCount + 1 - myKills; this.showEncouragement(killsNeeded); } } else if (this.wasKillLeader && !this.isKillLeader()) { // Détroné this.showEncouragement(0, true); this.wasKillLeader = false; } this.lastKnownKills = myKills; } } ;// ./src/HUD/GridSystem.ts class GridSystem { constructor() { this.gridSize = 20; // Size of each grid cell this.snapThreshold = 15; // Distance in pixels to trigger snap this.gridVisible = false; this.magneticEdges = true; this.gridContainer = this.createGridOverlay(); this.setupKeyBindings(); } createGridOverlay() { const container = document.createElement("div"); container.id = "grid-overlay"; Object.assign(container.style, { position: "fixed", top: "0", left: "0", width: "100%", height: "100%", pointerEvents: "none", zIndex: "9999", display: "none", opacity: "0.2", }); // Create vertical lines for (let x = this.gridSize; x < window.innerWidth; x += this.gridSize) { const vLine = document.createElement("div"); Object.assign(vLine.style, { position: "absolute", left: `${x}px`, top: "0", width: "1px", height: "100%", backgroundColor: "#4CAF50", }); container.appendChild(vLine); } // Create horizontal lines for (let y = this.gridSize; y < window.innerHeight; y += this.gridSize) { const hLine = document.createElement("div"); Object.assign(hLine.style, { position: "absolute", left: "0", top: `${y}px`, width: "100%", height: "1px", backgroundColor: "#4CAF50", }); container.appendChild(hLine); } document.body.appendChild(container); return container; } setupKeyBindings() { document.addEventListener("keydown", (e) => { if (e.key === "g" && e.altKey) { this.toggleGrid(); } }); } toggleGrid() { this.gridVisible = !this.gridVisible; this.gridContainer.style.display = this.gridVisible ? "block" : "none"; } snapToGrid(element, x, y) { const rect = element.getBoundingClientRect(); const elementWidth = rect.width; const elementHeight = rect.height; // Snap to grid let snappedX = Math.round(x / this.gridSize) * this.gridSize; let snappedY = Math.round(y / this.gridSize) * this.gridSize; // Edge snapping if (this.magneticEdges) { const screenEdges = { left: 0, right: window.innerWidth - elementWidth, center: (window.innerWidth - elementWidth) / 2, top: 0, bottom: window.innerHeight - elementHeight, middle: (window.innerHeight - elementHeight) / 2, }; // Snap to horizontal edges if (Math.abs(x - screenEdges.left) < this.snapThreshold) { snappedX = screenEdges.left; } else if (Math.abs(x - screenEdges.right) < this.snapThreshold) { snappedX = screenEdges.right; } else if (Math.abs(x - screenEdges.center) < this.snapThreshold) { snappedX = screenEdges.center; } // Snap to vertical edges if (Math.abs(y - screenEdges.top) < this.snapThreshold) { snappedY = screenEdges.top; } else if (Math.abs(y - screenEdges.bottom) < this.snapThreshold) { snappedY = screenEdges.bottom; } else if (Math.abs(y - screenEdges.middle) < this.snapThreshold) { snappedY = screenEdges.middle; } } return { x: snappedX, y: snappedY }; } highlightNearestGridLine(x, y) { if (!this.gridVisible) return; // Remove existing highlights const highlights = document.querySelectorAll(".grid-highlight"); highlights.forEach((h) => h.remove()); // Create highlight for nearest vertical line const nearestX = Math.round(x / this.gridSize) * this.gridSize; if (Math.abs(x - nearestX) < this.snapThreshold) { const vHighlight = document.createElement("div"); Object.assign(vHighlight.style, { position: "absolute", left: `${nearestX}px`, top: "0", width: "2px", height: "100%", backgroundColor: "#FFD700", zIndex: "10000", pointerEvents: "none", }); vHighlight.classList.add("grid-highlight"); this.gridContainer.appendChild(vHighlight); } // Create highlight for nearest horizontal line const nearestY = Math.round(y / this.gridSize) * this.gridSize; if (Math.abs(y - nearestY) < this.snapThreshold) { const hHighlight = document.createElement("div"); Object.assign(hHighlight.style, { position: "absolute", left: "0", top: `${nearestY}px`, width: "100%", height: "2px", backgroundColor: "#FFD700", zIndex: "10000", pointerEvents: "none", }); hHighlight.classList.add("grid-highlight"); this.gridContainer.appendChild(hHighlight); } } } ;// ./src/SERVER/DiscordTracking.ts var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const stuff_emojis = { main_weapon: "🔫", secondary_weapon: "🔫", grenades: "💣", melees: "🔪", soda: "🥤", medkit: "🩹", bandage: "🩹", pills: "💊", backpack: "🎒", chest: "📦", helmet: "⛑️" }; class WebhookValidator { static isValidWebhookUrl(url) { return url.startsWith("https://"); } static isWebhookAlive(webhookUrl) { return __awaiter(this, void 0, void 0, function* () { try { // First check if the URL format is valid if (!this.isValidWebhookUrl(webhookUrl)) { throw new Error("Invalid webhook URL format"); } // Test the webhook with a GET request (Discord allows GET on webhooks) const response = yield fetch(webhookUrl, { method: "GET", headers: { "Content-Type": "application/json", }, }); // Discord returns 200 for valid webhooks return response.status === 200; } catch (error) { return false; } }); } static testWebhook(webhookUrl) { return __awaiter(this, void 0, void 0, function* () { try { if (!webhookUrl) { return { isValid: false, message: "Please enter a webhook URL", }; } if (!this.isValidWebhookUrl(webhookUrl)) { return { isValid: false, message: "Invalid Discord webhook URL format", }; } const isAlive = yield this.isWebhookAlive(webhookUrl); return { isValid: isAlive, message: isAlive ? "Webhook is valid and working!" : "Webhook is not responding or has been deleted", }; } catch (error) { return { isValid: false, message: "Error testing webhook connection", }; } }); } } class DiscordTracking { constructor(kxsClient, webhookUrl) { this.kxsClient = kxsClient; this.webhookUrl = webhookUrl; } setWebhookUrl(webhookUrl) { this.webhookUrl = webhookUrl; } validateCurrentWebhook() { return __awaiter(this, void 0, void 0, function* () { return WebhookValidator.isWebhookAlive(this.webhookUrl); }); } sendWebhookMessage(message) { return __awaiter(this, void 0, void 0, function* () { if (!WebhookValidator.isValidWebhookUrl(this.webhookUrl)) { return; } this.kxsClient.nm.showNotification("Sending Discord message...", "info", 2300); try { const response = yield fetch(this.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(message), }); if (!response.ok) { throw new Error(`Discord Webhook Error: ${response.status}`); } } catch (error) { this.kxsClient.logger.error("Error sending Discord message:", error); } }); } getEmbedColor(isWin) { return isWin ? 0x2ecc71 : 0xe74c3c; // Green for victory, red for defeat } trackGameEnd(result) { return __awaiter(this, void 0, void 0, function* () { const title = result.isWin ? "🏆 VICTORY ROYALE!" : `${result.position} - Game Over`; const embed = { title, description: `${result.username}'s Match`, color: this.getEmbedColor(result.isWin), fields: [ { name: "💀 Eliminations", value: result.kills.toString(), inline: true, }, ], }; if (result.duration) { embed.fields.push({ name: "⏱️ Duration", value: result.duration, inline: true, }); } if (result.damageDealt) { embed.fields.push({ name: "💥 Damage Dealt", value: Math.round(result.damageDealt).toString(), inline: true, }); } if (result.damageTaken) { embed.fields.push({ name: "💢 Damage Taken", value: Math.round(result.damageTaken).toString(), inline: true, }); } if (result.username) { embed.fields.push({ name: "📝 Username", value: result.username, inline: true, }); } if (result.stuff) { for (const [key, value] of Object.entries(result.stuff)) { if (value) { embed.fields.push({ name: `${stuff_emojis[key]} ${key.replace("_", " ").toUpperCase()}`, value, inline: true, }); } } } const message = { username: "KxsClient", avatar_url: kxs_logo, content: result.isWin ? "🎉 New Victory!" : "Match Ended", embeds: [embed], }; yield this.sendWebhookMessage(message); }); } } ;// ./src/FUNC/StatsParser.ts class StatsParser { static cleanNumber(str) { return parseInt(str.replace(/[^\d.-]/g, "")) || 0; } /** * Extract the full duration string including the unit */ static extractDuration(str) { const match = str.match(/(\d+\s*[smh])/i); return match ? match[1].trim() : "0s"; } static parse(statsText, rankContent) { let stats = { username: "Player", kills: 0, damageDealt: 0, damageTaken: 0, duration: "", position: "#unknown", }; // Handle developer format const devPattern = /Developer.*?Kills(\d+).*?Damage Dealt(\d+).*?Damage Taken(\d+).*?Survived(\d+\s*[smh])/i; const devMatch = statsText.match(devPattern); if (devMatch) { return { username: "Player", kills: this.cleanNumber(devMatch[1]), damageDealt: this.cleanNumber(devMatch[2]), damageTaken: this.cleanNumber(devMatch[3]), duration: devMatch[4].trim(), // Keep the full duration string with unit position: rankContent.replace("##", "#"), }; } // Handle template format const templatePattern = /%username%.*?Kills%kills_number%.*?Dealt%number_dealt%.*?Taken%damage_taken%.*?Survived%duration%/; const templateMatch = statsText.match(templatePattern); if (templateMatch) { const parts = statsText.split(/Kills|Dealt|Taken|Survived/); if (parts.length >= 5) { return { username: parts[0].trim(), kills: this.cleanNumber(parts[1]), damageDealt: this.cleanNumber(parts[2]), damageTaken: this.cleanNumber(parts[3]), duration: this.extractDuration(parts[4]), // Extract full duration with unit position: rankContent.replace("##", "#"), }; } } // Generic parsing as fallback const usernameMatch = statsText.match(/^([^0-9]+)/); if (usernameMatch) { stats.username = usernameMatch[1].trim(); } const killsMatch = statsText.match(/Kills[^0-9]*(\d+)/i); if (killsMatch) { stats.kills = this.cleanNumber(killsMatch[1]); } const dealtMatch = statsText.match(/Dealt[^0-9]*(\d+)/i); if (dealtMatch) { stats.damageDealt = this.cleanNumber(dealtMatch[1]); } const takenMatch = statsText.match(/Taken[^0-9]*(\d+)/i); if (takenMatch) { stats.damageTaken = this.cleanNumber(takenMatch[1]); } // Extract survival time with unit const survivalMatch = statsText.match(/Survived[^0-9]*(\d+\s*[smh])/i); if (survivalMatch) { stats.duration = survivalMatch[1].trim(); } stats.position = rankContent.replace("##", "#"); return stats; } } // EXTERNAL MODULE: ./node_modules/semver/functions/gt.js var gt = __webpack_require__(580); var gt_default = /*#__PURE__*/__webpack_require__.n(gt); ;// ./package.json const package_namespaceObject = {"rE":"2.0.6"}; ;// ./src/FUNC/UpdateChecker.ts var UpdateChecker_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; class UpdateChecker { constructor(kxsClient) { this.remoteScriptUrl = config_namespaceObject.api_url + "/getLatestVersion"; this.kxsClient = kxsClient; if (this.kxsClient.isAutoUpdateEnabled) { this.checkForUpdate(); } } downloadScript() { return UpdateChecker_awaiter(this, void 0, void 0, function* () { try { const response = yield fetch(this.remoteScriptUrl, { method: "GET", headers: { "cache-control": "no-cache, no-store, must-revalidate", "pragma": "no-cache", "expires": "0" } }); if (!response.ok) { throw new Error("Error downloading script: " + response.statusText); } const blob = yield response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = downloadUrl; downloadLink.download = 'KxsClient.user.js'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); window.URL.revokeObjectURL(downloadUrl); } catch (error) { throw new Error("Error during script download: " + error); } }); } getNewScriptVersion() { return UpdateChecker_awaiter(this, void 0, void 0, function* () { try { const response = yield fetch(this.remoteScriptUrl, { method: "GET", headers: { "cache-control": "no-cache, no-store, must-revalidate", "pragma": "no-cache", "expires": "0" } }); if (!response.ok) { throw new Error("Error retrieving remote script: " + response.statusText); } const scriptContent = yield response.text(); const versionMatch = scriptContent.match(/\/\/\s*@version\s+([\d.]+)/); if (versionMatch && versionMatch[1]) { return versionMatch[1]; } else { throw new Error("Script version was not found in the file."); } } catch (error) { throw new Error("Error retrieving remote script: " + error); } }); } checkForUpdate() { return UpdateChecker_awaiter(this, void 0, void 0, function* () { const localScriptVersion = yield this.getCurrentScriptVersion(); const hostedScriptVersion = yield this.getNewScriptVersion(); this.hostedScriptVersion = hostedScriptVersion; // Vérifie si la version hébergée est supérieure à la version locale if (gt_default()(hostedScriptVersion, localScriptVersion)) { this.displayUpdateNotification(); } else { this.kxsClient.nm.showNotification("Client is up to date", "success", 2300); } }); } displayUpdateNotification() { const modal = document.createElement("div"); modal.style.position = "fixed"; modal.style.top = "50%"; modal.style.left = "50%"; modal.style.transform = "translate(-50%, -50%)"; modal.style.backgroundColor = "rgb(250, 250, 250)"; modal.style.borderRadius = "10px"; modal.style.padding = "20px"; modal.style.width = "400px"; modal.style.boxShadow = "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"; modal.style.border = "1px solid rgb(229, 229, 229)"; const header = document.createElement("div"); header.style.display = "flex"; header.style.alignItems = "center"; header.style.marginBottom = "15px"; const title = document.createElement("h3"); title.textContent = "Download Update"; title.style.margin = "0"; title.style.fontSize = "16px"; title.style.fontWeight = "600"; header.appendChild(title); const closeButton = document.createElement("button"); closeButton.innerHTML = "×"; closeButton.style.marginLeft = "auto"; closeButton.style.border = "none"; closeButton.style.background = "none"; closeButton.style.fontSize = "20px"; closeButton.style.cursor = "pointer"; closeButton.style.padding = "0 5px"; closeButton.onclick = () => modal.remove(); header.appendChild(closeButton); const content = document.createElement("div"); content.innerHTML = `A new version of KxsClient is available!
Locale: ${this.getCurrentScriptVersion()} | On web: ${this.hostedScriptVersion}
Click the button below to update now.`; content.style.marginBottom = "20px"; content.style.color = "rgb(75, 85, 99)"; const updateButton = document.createElement("button"); updateButton.textContent = "Update Now"; updateButton.style.backgroundColor = "rgb(59, 130, 246)"; updateButton.style.color = "white"; updateButton.style.padding = "8px 16px"; updateButton.style.borderRadius = "6px"; updateButton.style.border = "none"; updateButton.style.cursor = "pointer"; updateButton.style.width = "100%"; updateButton.onclick = () => UpdateChecker_awaiter(this, void 0, void 0, function* () { try { yield this.downloadScript(); this.kxsClient.nm.showNotification("Download started", "success", 2300); modal.remove(); } catch (error) { this.kxsClient.nm.showNotification("Download failed: " + error.message, "info", 5000); } }); modal.appendChild(header); modal.appendChild(content); modal.appendChild(updateButton); document.body.appendChild(modal); } getCurrentScriptVersion() { return package_namespaceObject.rE; } } ;// ./src/SERVER/DiscordRichPresence.ts class DiscordWebSocket { constructor(kxsClient, token) { this.ws = null; this.heartbeatInterval = 0; this.sequence = null; this.isAuthenticated = false; this.kxsClient = kxsClient; } connect() { if (this.kxsClient.discordToken === "" || this.kxsClient.discordToken === null || this.kxsClient.discordToken === undefined) { return; } this.ws = new WebSocket('wss://gateway.discord.gg/?v=9&encoding=json'); this.ws.onopen = () => { this.kxsClient.logger.log('[RichPresence] WebSocket connection established'); }; this.ws.onmessage = (event) => { const data = JSON.parse(event.data); this.handleMessage(data); }; this.ws.onerror = (error) => { this.kxsClient.nm.showNotification('WebSocket error: ' + error.type, 'error', 5000); }; this.ws.onclose = () => { this.kxsClient.nm.showNotification('Disconnected from Discord gateway', 'info', 5000); clearInterval(this.heartbeatInterval); this.isAuthenticated = false; }; } identify() { const payload = { op: 2, d: { token: this.kxsClient.discordToken, properties: { $os: 'linux', $browser: 'chrome', $device: 'chrome' }, presence: { activities: [{ name: "KxsClient", type: 0, application_id: "1321193265533550602", assets: { large_image: "mp:app-icons/1321193265533550602/bccd2479ec56ed7d4e69fa2fdfb47197.png?size=512", large_text: "KxsClient v" + package_namespaceObject.rE, } }], status: 'online', afk: false } } }; this.send(payload); } handleMessage(data) { switch (data.op) { case 10: // Hello const { heartbeat_interval } = data.d; this.startHeartbeat(heartbeat_interval); this.identify(); this.kxsClient.nm.showNotification('Started Discord RPC', 'success', 3000); break; case 11: // Heartbeat ACK this.kxsClient.logger.log('[RichPresence] Heartbeat acknowledged'); break; case 0: // Dispatch this.sequence = data.s; if (data.t === 'READY') { this.isAuthenticated = true; this.kxsClient.nm.showNotification('Connected to Discord gateway', 'success', 2500); } break; } } startHeartbeat(interval) { this.heartbeatInterval = setInterval(() => { this.send({ op: 1, d: this.sequence }); }, interval); } send(data) { var _a; if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } } disconnect() { if (this.ws) { clearInterval(this.heartbeatInterval); this.ws.close(); } } } ;// ./src/HUD/MOD/NotificationManager.ts class NotificationManager { constructor() { this.notifications = []; this.NOTIFICATION_HEIGHT = 65; // Height + margin this.NOTIFICATION_MARGIN = 10; this.addGlobalStyles(); } static getInstance() { if (!NotificationManager.instance) { NotificationManager.instance = new NotificationManager(); } return NotificationManager.instance; } addGlobalStyles() { const styleSheet = document.createElement("style"); styleSheet.textContent = ` @keyframes slideIn { 0% { transform: translateX(-120%); opacity: 0; } 50% { transform: translateX(10px); opacity: 0.8; } 100% { transform: translateX(0); opacity: 1; } } @keyframes slideOut { 0% { transform: translateX(0); opacity: 1; } 50% { transform: translateX(10px); opacity: 0.8; } 100% { transform: translateX(-120%); opacity: 0; } } @keyframes slideLeft { from { transform-origin: right; transform: scaleX(1); } to { transform-origin: right; transform: scaleX(0); } } @keyframes bounce { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } `; document.head.appendChild(styleSheet); } updateNotificationPositions() { this.notifications.forEach((notification, index) => { const topPosition = 20 + (index * this.NOTIFICATION_HEIGHT); notification.style.top = `${topPosition}px`; }); } removeNotification(notification) { const index = this.notifications.indexOf(notification); if (index > -1) { this.notifications.splice(index, 1); this.updateNotificationPositions(); } } getIconConfig(type) { const configs = { success: { color: '#4CAF50', svg: ` ` }, error: { color: '#F44336', svg: ` ` }, info: { color: '#FFD700', svg: ` ` } }; return configs[type]; } showNotification(message, type, duration = 5000) { const notification = document.createElement("div"); // Base styles Object.assign(notification.style, { position: "fixed", top: "20px", left: "20px", padding: "12px 20px", backgroundColor: "#333333", color: "white", zIndex: "9999", minWidth: "200px", borderRadius: "4px", display: "flex", alignItems: "center", gap: "10px", transform: "translateX(-120%)", opacity: "0", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)" }); // Create icon const icon = document.createElement("div"); Object.assign(icon.style, { width: "20px", height: "20px", display: "flex", alignItems: "center", justifyContent: "center", animation: "bounce 0.5s ease-in-out" }); const iconConfig = this.getIconConfig(type); icon.style.color = iconConfig.color; icon.innerHTML = iconConfig.svg; // Create message const messageDiv = document.createElement("div"); messageDiv.textContent = message; messageDiv.style.flex = "1"; // Create progress bar const progressBar = document.createElement("div"); Object.assign(progressBar.style, { height: "4px", backgroundColor: "#e6f3ff", width: "100%", position: "absolute", bottom: "0", left: "0", animation: `slideLeft ${duration}ms linear forwards` }); // Assemble notification notification.appendChild(icon); notification.appendChild(messageDiv); notification.appendChild(progressBar); document.body.appendChild(notification); // Add to stack and update positions this.notifications.push(notification); this.updateNotificationPositions(); // Entrance animation requestAnimationFrame(() => { notification.style.transition = "all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)"; notification.style.animation = "slideIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards"; }); // Exit animation and cleanup setTimeout(() => { notification.style.animation = "slideOut 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards"; setTimeout(() => { this.removeNotification(notification); notification.remove(); }, 500); }, duration); } } ;// ./src/HUD/LegacyClientSecondaryMenu.ts class KxsLegacyClientSecondaryMenu { constructor(kxsClient) { this.kxsClient = kxsClient; this.isClientMenuVisible = false; this.isDragging = false; this.dragOffset = { x: 0, y: 0 }; this.sections = []; this.menu = document.createElement("div"); this.isOpen = false; this.boundShiftListener = this.handleShiftPress.bind(this); this.boundEscapeListener = this.handleEscapePress.bind(this); this.boundMouseDownListener = this.handleMouseDown.bind(this); this.boundMouseMoveListener = this.handleMouseMove.bind(this); this.boundMouseUpListener = this.handleMouseUp.bind(this); this.initMenu(); this.addShiftListener(); this.addDragListeners(); this.loadOption(); } handleShiftPress(event) { if (event.key === "Shift" && event.location == 2) { // this.clearMenu(); this.toggleMenuVisibility(); } } handleEscapePress(event) { if (event.key === "Escape" && this.isClientMenuVisible) { // Fermer le menu si la touche Échap est pressée et que le menu est visible this.toggleMenuVisibility(); // Empêcher la propagation ET l'action par défaut event.stopPropagation(); event.preventDefault(); // Arrêter complètement la propagation de l'événement return false; } } handleMouseDown(e) { // Empêcher la propagation de l'événement mousedown vers la page web // pour TOUS les éléments du menu, y compris les éléments interactifs e.stopPropagation(); // Activer le drag & drop seulement si on clique sur une zone non interactive if (e.target instanceof HTMLElement && !e.target.matches("input, select, button")) { this.isDragging = true; const rect = this.menu.getBoundingClientRect(); this.dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top, }; this.menu.style.cursor = "grabbing"; } } handleMouseMove(e) { if (!this.isDragging) return; e.preventDefault(); const newX = e.clientX - this.dragOffset.x; const newY = e.clientY - this.dragOffset.y; const maxX = window.innerWidth - this.menu.offsetWidth; const maxY = window.innerHeight - this.menu.offsetHeight; this.menu.style.left = `${Math.max(0, Math.min(newX, maxX))}px`; this.menu.style.top = `${Math.max(0, Math.min(newY, maxY))}px`; } handleMouseUp(e) { // Arrêter le drag & drop const wasDragging = this.isDragging; this.isDragging = false; this.menu.style.cursor = "move"; // Empêcher la propagation de l'événement mouseup vers la page web // pour tous les éléments du menu, y compris les éléments interactifs if (this.menu.contains(e.target)) { e.stopPropagation(); } } initMenu() { this.menu.id = "kxsMenuIG"; this.applyMenuStyles(); this.createHeader(); document.body.appendChild(this.menu); // Empêcher la propagation des événements souris (clics et molette) vers la page web // Utiliser la phase de bouillonnement (bubbling) au lieu de la phase de capture // pour permettre aux éléments enfants de recevoir les événements d'abord this.menu.addEventListener('click', (e) => { e.stopPropagation(); }); this.menu.addEventListener('wheel', (e) => { e.stopPropagation(); }); } loadOption() { let HUD = this.addSection("HUD"); let SOUND = this.addSection("SOUND"); this.addOption(SOUND, { label: "Win sound", value: this.kxsClient.soundLibrary.win_sound_url, type: "input", onChange: (value) => { this.kxsClient.soundLibrary.win_sound_url = value; this.kxsClient.updateLocalStorage(); } }); this.addOption(SOUND, { label: "Death sound", value: this.kxsClient.soundLibrary.death_sound_url, type: "input", onChange: (value) => { this.kxsClient.soundLibrary.death_sound_url = value; this.kxsClient.updateLocalStorage(); } }); this.addOption(HUD, { label: "Use Legacy Menu", value: this.kxsClient.isLegaySecondaryMenu, type: "toggle", onChange: (value) => { this.kxsClient.isLegaySecondaryMenu = !this.kxsClient.isLegaySecondaryMenu; this.kxsClient.updateLocalStorage(); this.kxsClient.secondaryMenu = new KxsClientSecondaryMenu(this.kxsClient); this.destroy(); }, }); this.addOption(HUD, { label: "Clean Main Menu", value: this.kxsClient.isMainMenuCleaned, type: "toggle", onChange: (value) => { this.kxsClient.isMainMenuCleaned = !this.kxsClient.isMainMenuCleaned; this.kxsClient.MainMenuCleaning(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Message Open/Close RSHIFT Menu", value: this.kxsClient.isNotifyingForToggleMenu, type: "toggle", onChange: (value) => { this.kxsClient.isNotifyingForToggleMenu = !this.kxsClient.isNotifyingForToggleMenu; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show Ping", value: this.kxsClient.isPingVisible, type: "toggle", onChange: (value) => { this.kxsClient.isPingVisible = !this.kxsClient.isPingVisible; this.kxsClient.updatePingVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show FPS", value: this.kxsClient.isFpsVisible, type: "toggle", onChange: (value) => { this.kxsClient.isFpsVisible = !this.kxsClient.isFpsVisible; this.kxsClient.updateFpsVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show Kills", value: this.kxsClient.isKillsVisible, type: "toggle", onChange: (value) => { this.kxsClient.isKillsVisible = !this.kxsClient.isKillsVisible; this.kxsClient.updateKillsVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Kill Feed Chroma", value: this.kxsClient.isKillFeedBlint, type: "toggle", onChange: (value) => { this.kxsClient.isKillFeedBlint = !this.kxsClient.isKillFeedBlint; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleKillFeed(); }, }); this.addOption(HUD, { label: "Weapon Border", value: this.kxsClient.isGunOverlayColored, type: "toggle", onChange: () => { this.kxsClient.isGunOverlayColored = !this.kxsClient.isGunOverlayColored; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleWeaponBorderHandler(); }, }); this.addOption(HUD, { label: "Chromatic Weapon Border", value: this.kxsClient.isGunBorderChromatic, type: "toggle", onChange: () => { this.kxsClient.isGunBorderChromatic = !this.kxsClient.isGunBorderChromatic; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleChromaticWeaponBorder(); if (this.kxsClient.isGunOverlayColored) { this.kxsClient.hud.toggleWeaponBorderHandler(); } }, }); this.addOption(HUD, { label: "Custom Crosshair", value: this.kxsClient.customCrosshair || "", type: "input", onChange: (value) => { this.kxsClient.customCrosshair = value; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.loadCustomCrosshair(); }, }); let miscSection = this.addSection("Misc"); this.addOption(miscSection, { label: "Gameplay History", value: true, type: "click", onChange: (value) => { this.kxsClient.historyManager.show(); }, }); let musicSection = this.addSection("Music"); this.addOption(musicSection, { label: "Death sound", value: this.kxsClient.isDeathSoundEnabled, type: "toggle", onChange: (value) => { this.kxsClient.isDeathSoundEnabled = !this.kxsClient.isDeathSoundEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(musicSection, { label: "Win sound", value: this.kxsClient.isWinSoundEnabled, type: "toggle", onChange: (value) => { this.kxsClient.isWinSoundEnabled = !this.kxsClient.isWinSoundEnabled; this.kxsClient.updateLocalStorage(); }, }); let pluginsSection = this.addSection("Plugins"); this.addOption(pluginsSection, { label: "Webhook URL", value: this.kxsClient.discordWebhookUrl || "", type: "input", onChange: (value) => { value = value.toString().trim(); this.kxsClient.discordWebhookUrl = value; this.kxsClient.discordTracker.setWebhookUrl(value); this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: "Heal Warning", value: this.kxsClient.isHealthWarningEnabled, type: "toggle", onChange: (value) => { var _a, _b; this.kxsClient.isHealthWarningEnabled = !this.kxsClient.isHealthWarningEnabled; if (this.kxsClient.isHealthWarningEnabled) { // Always enter placement mode when enabling from RSHIFT menu (_a = this.kxsClient.healWarning) === null || _a === void 0 ? void 0 : _a.enableDragging(); } else { (_b = this.kxsClient.healWarning) === null || _b === void 0 ? void 0 : _b.hide(); } this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: "Update Checker", value: this.kxsClient.isAutoUpdateEnabled, type: "toggle", onChange: (value) => { this.kxsClient.isAutoUpdateEnabled = !this.kxsClient.isAutoUpdateEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: `Spotify Player`, value: this.kxsClient.isSpotifyPlayerEnabled, type: "toggle", onChange: () => { this.kxsClient.isSpotifyPlayerEnabled = !this.kxsClient.isSpotifyPlayerEnabled; this.kxsClient.updateLocalStorage(); this.kxsClient.toggleSpotifyMenu(); }, }); this.addOption(pluginsSection, { label: `Uncap FPS`, value: this.kxsClient.isFpsUncapped, type: "toggle", onChange: () => { this.kxsClient.isFpsUncapped = !this.kxsClient.isFpsUncapped; this.kxsClient.setAnimationFrameCallback(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: `Winning Animation`, value: this.kxsClient.isWinningAnimationEnabled, type: "toggle", onChange: () => { this.kxsClient.isWinningAnimationEnabled = !this.kxsClient.isWinningAnimationEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: `Rich Presence (Account token required)`, value: this.kxsClient.discordToken || "", type: "input", onChange: (value) => { value = value.toString().trim(); this.kxsClient.discordToken = this.kxsClient.parseToken(value); this.kxsClient.discordRPC.disconnect(); this.kxsClient.discordRPC.connect(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: `Kill Leader Tracking`, value: this.kxsClient.isKillLeaderTrackerEnabled, type: "toggle", onChange: (value) => { this.kxsClient.isKillLeaderTrackerEnabled = !this.kxsClient.isKillLeaderTrackerEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(pluginsSection, { label: `Friends Detector (separe with ',')`, value: this.kxsClient.all_friends, type: "input", onChange: (value) => { this.kxsClient.all_friends = value; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: `Change Background`, value: true, type: "click", onChange: () => { const backgroundElement = document.getElementById("background"); if (!backgroundElement) { alert("Element with id 'background' not found."); return; } const choice = prompt("Enter '0' to default Kxs background, '1' to provide a URL or '2' to upload a local image:"); if (choice === "0") { localStorage.removeItem("lastBackgroundUrl"); localStorage.removeItem("lastBackgroundFile"); localStorage.removeItem("lastBackgroundType"); localStorage.removeItem("lastBackgroundValue"); } else if (choice === "1") { const newBackgroundUrl = prompt("Enter the URL of the new background image:"); if (newBackgroundUrl) { backgroundElement.style.backgroundImage = `url(${newBackgroundUrl})`; this.kxsClient.saveBackgroundToLocalStorage(newBackgroundUrl); alert("Background updated successfully!"); } } else if (choice === "2") { const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.accept = "image/*"; fileInput.onchange = (event) => { var _a, _b; const file = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]; if (file) { const reader = new FileReader(); reader.onload = () => { backgroundElement.style.backgroundImage = `url(${reader.result})`; this.kxsClient.saveBackgroundToLocalStorage(file); alert("Background updated successfully!"); }; reader.readAsDataURL(file); } }; fileInput.click(); } }, }); } clearMenu() { this.sections.forEach((section) => { if (section.element) { section.element.remove(); } }); this.sections = []; } applyMenuStyles() { Object.assign(this.menu.style, { backgroundColor: "rgba(30, 30, 30, 0.95)", padding: "15px", borderRadius: "10px", boxShadow: "0 4px 15px rgba(0, 0, 0, 0.7)", zIndex: "10001", width: "300px", fontFamily: "Arial, sans-serif", color: "#fff", maxHeight: "500px", overflowY: "auto", position: "fixed", top: "15%", left: "10%", cursor: "move", display: "none", }); } createHeader() { const title = document.createElement("h2"); title.textContent = "KxsClient v" + package_namespaceObject.rE; Object.assign(title.style, { margin: "0 0 10px", textAlign: "center", fontSize: "18px", color: "#FFAE00", }); this.menu.appendChild(title); } addSection(title) { const section = { title, options: [], }; const sectionElement = document.createElement("div"); sectionElement.className = "menu-section"; const sectionTitle = document.createElement("h3"); sectionTitle.textContent = title; Object.assign(sectionTitle.style, { margin: "15px 0 10px", fontSize: "16px", color: "#4CAF50", }); sectionElement.appendChild(sectionTitle); this.menu.appendChild(sectionElement); // Stocker la référence à l'élément DOM section.element = sectionElement; this.sections.push(section); return section; } addOption(section, option) { section.options.push(option); const optionDiv = document.createElement("div"); Object.assign(optionDiv.style, { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "8px", padding: "4px", borderRadius: "4px", backgroundColor: "rgba(255, 255, 255, 0.1)", }); const label = document.createElement("span"); label.textContent = option.label; label.style.color = "#fff"; let valueElement = null; switch (option.type) { case "toggle": valueElement = this.createToggleElement(option); break; case "input": valueElement = this.createInputElement(option); break; case "click": valueElement = this.createClickElement(option); break; } optionDiv.appendChild(label); optionDiv.appendChild(valueElement); // Utiliser la référence stockée à l'élément de section if (section.element) { section.element.appendChild(optionDiv); } } createToggleElement(option) { const toggle = document.createElement("div"); toggle.style.cursor = "pointer"; toggle.style.color = option.value ? "#4CAF50" : "#ff4444"; toggle.textContent = String(option.value); toggle.addEventListener("click", () => { var _a; const newValue = !option.value; option.value = newValue; toggle.textContent = String(newValue); toggle.style.color = newValue ? "#4CAF50" : "#ff4444"; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, newValue); }); return toggle; } createClickElement(option) { const button = document.createElement("button"); button.textContent = option.label; button.style.backgroundColor = "rgba(255, 255, 255, 0.1)"; button.style.border = "none"; button.style.borderRadius = "3px"; button.style.color = "#FFAE00"; button.style.padding = "2px 5px"; button.style.cursor = "pointer"; button.style.fontSize = "12px"; button.addEventListener("click", () => { var _a; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, true); }); return button; } createInputElement(option) { const input = document.createElement("input"); input.type = "text"; input.value = String(option.value); Object.assign(input.style, { backgroundColor: "rgba(255, 255, 255, 0.1)", border: "none", borderRadius: "3px", color: "#FFAE00", padding: "2px 5px", width: "60px", textAlign: "right", }); input.addEventListener("change", () => { var _a; option.value = input.value; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, input.value); }); // Empêcher la propagation des touches de texte vers la page web // mais permettre l'interaction avec l'input input.addEventListener("keydown", (e) => { e.stopPropagation(); }); input.addEventListener("keyup", (e) => { e.stopPropagation(); }); input.addEventListener("keypress", (e) => { e.stopPropagation(); }); // Empêcher la propagation des événements de souris input.addEventListener("mousedown", (e) => { e.stopPropagation(); }); input.addEventListener("click", (e) => { e.stopPropagation(); }); return input; } addShiftListener() { // Gestionnaire pour la touche Shift (ouverture du menu) window.addEventListener("keydown", this.boundShiftListener); // Utiliser la phase de capture pour intercepter l'événement Escape // avant qu'il n'atteigne le jeu document.addEventListener("keydown", this.boundEscapeListener, true); } addDragListeners() { this.menu.addEventListener("mousedown", this.boundMouseDownListener); window.addEventListener("mousemove", this.boundMouseMoveListener); window.addEventListener("mouseup", this.boundMouseUpListener); } toggleMenuVisibility() { this.isClientMenuVisible = !this.isClientMenuVisible; // Mettre à jour la propriété publique en même temps this.isOpen = this.isClientMenuVisible; if (this.kxsClient.isNotifyingForToggleMenu) { this.kxsClient.nm.showNotification(this.isClientMenuVisible ? "Opening menu..." : "Closing menu...", "info", 1400); } this.menu.style.display = this.isClientMenuVisible ? "block" : "none"; } destroy() { // Remove event listeners window.removeEventListener("keydown", this.boundShiftListener); document.removeEventListener("keydown", this.boundEscapeListener, true); this.menu.removeEventListener("mousedown", this.boundMouseDownListener); window.removeEventListener("mousemove", this.boundMouseMoveListener); window.removeEventListener("mouseup", this.boundMouseUpListener); // Remove all section elements and clear sections array this.sections.forEach(section => { if (section.element) { // Remove all option elements within the section const optionElements = section.element.querySelectorAll("div"); optionElements.forEach(element => { // Remove event listeners from toggle and input elements const interactive = element.querySelector("div, input"); if (interactive) { interactive.replaceWith(interactive.cloneNode(true)); } element.remove(); }); section.element.remove(); } }); this.sections = []; // Remove the menu from DOM this.menu.remove(); // Reset instance variables this.isClientMenuVisible = false; this.isDragging = false; this.dragOffset = { x: 0, y: 0 }; this.menu = null; // Clear references this.kxsClient = null; this.boundShiftListener = null; this.boundMouseDownListener = null; this.boundMouseMoveListener = null; this.boundMouseUpListener = null; } getMenuVisibility() { return this.isClientMenuVisible; } } ;// ./src/HUD/ClientSecondaryMenuRework.ts const category = ["ALL", "HUD", "SERVER", "MECHANIC", "SOUND", "MISC"]; class KxsClientSecondaryMenu { constructor(kxsClient) { this.searchTerm = ''; this.shiftListener = (event) => { if (event.key === "Shift" && event.location == 2) { this.clearMenu(); this.toggleMenuVisibility(); // Ensure options are displayed after loading this.filterOptions(); } }; this.mouseMoveListener = (e) => { if (this.isDragging) { const x = e.clientX - this.dragOffset.x; const y = e.clientY - this.dragOffset.y; this.menu.style.transform = 'none'; this.menu.style.left = `${x}px`; this.menu.style.top = `${y}px`; } }; this.mouseUpListener = () => { this.isDragging = false; this.menu.style.cursor = "grab"; }; this.kxsClient = kxsClient; this.isClientMenuVisible = false; this.isDragging = false; this.dragOffset = { x: 0, y: 0 }; this.sections = []; this.allOptions = []; this.activeCategory = "ALL"; this.isOpen = false; this.menu = document.createElement("div"); this.initMenu(); this.addShiftListener(); this.addDragListeners(); this.loadOption(); } initMenu() { this.menu.id = "kxsClientMenu"; this.applyMenuStyles(); this.createHeader(); this.createGridContainer(); document.body.appendChild(this.menu); this.menu.style.display = "none"; // Empêcher la propagation des événements souris (clics et molette) vers la page web // Utiliser la phase de bouillonnement (bubbling) au lieu de la phase de capture // pour permettre aux éléments enfants de recevoir les événements d'abord this.menu.addEventListener('click', (e) => { e.stopPropagation(); }); this.menu.addEventListener('wheel', (e) => { e.stopPropagation(); }); // Nous ne gérons pas mousedown et mouseup ici car ils sont gérés dans addDragListeners() } applyMenuStyles() { // Styles par défaut (desktop/tablette) const defaultStyles = { backgroundColor: "rgba(17, 24, 39, 0.95)", padding: "20px", borderRadius: "12px", boxShadow: "0 4px 20px rgba(0, 0, 0, 0.8)", zIndex: "10001", width: "800px", fontFamily: "'Segoe UI', Arial, sans-serif", color: "#fff", maxHeight: "80vh", overflowY: "auto", overflowX: "hidden", // Prevent horizontal scrolling position: "fixed", top: "10%", left: "50%", transform: "translateX(-50%)", display: "none", boxSizing: "border-box", // Include padding in width calculation }; // Styles réduits pour mobile const mobileStyles = { padding: "6px", borderRadius: "7px", width: "78vw", maxWidth: "84vw", fontSize: "10px", maxHeight: "60vh", top: "4%", left: "50%", }; Object.assign(this.menu.style, defaultStyles); if (this.kxsClient.isMobile && this.kxsClient.isMobile()) { Object.assign(this.menu.style, mobileStyles); } } blockMousePropagation(element, preventDefault = true) { ['click', 'mousedown', 'mouseup', 'dblclick', 'contextmenu', 'wheel'].forEach(eventType => { element.addEventListener(eventType, (e) => { e.stopPropagation(); if (preventDefault && (eventType === 'contextmenu' || eventType === 'wheel' || element.tagName !== 'INPUT')) { e.preventDefault(); } }, false); }); } createHeader() { const header = document.createElement("div"); // Détection mobile pour styles réduits const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile(); const logoSize = isMobile ? 16 : 24; const titleFontSize = isMobile ? 12 : 20; const headerGap = isMobile ? 4 : 10; const headerMarginBottom = isMobile ? 8 : 20; const closeBtnPadding = isMobile ? 2 : 6; const closeBtnFontSize = isMobile ? 12 : 18; header.style.marginBottom = `${headerMarginBottom}px`; header.innerHTML = `
Logo KXS CLIENT
${category.map(cat => ` `).join('')}
`; header.querySelectorAll('.category-btn').forEach(btn => { this.blockMousePropagation(btn); btn.addEventListener('click', (e) => { const category = e.target.dataset.category; if (category) { this.setActiveCategory(category); } }); }); const closeButton = header.querySelector('button'); closeButton === null || closeButton === void 0 ? void 0 : closeButton.addEventListener('click', () => { this.toggleMenuVisibility(); }); const searchInput = header.querySelector('#kxsSearchInput'); if (searchInput) { this.blockMousePropagation(searchInput, false); // Gestionnaire pour mettre à jour la recherche searchInput.addEventListener('input', (e) => { this.searchTerm = e.target.value.toLowerCase(); this.filterOptions(); }); // Prevent keys from being interpreted by the game // We only block the propagation of keyboard events, except for special keys ['keydown', 'keyup', 'keypress'].forEach(eventType => { searchInput.addEventListener(eventType, (e) => { const keyEvent = e; // Don't block special keys (Escape, Shift) if (keyEvent.key === 'Escape' || (keyEvent.key === 'Shift' && keyEvent.location === 2)) { return; // Let the event propagate normally } // Block propagation for all other keys e.stopPropagation(); }); }); // Éviter que la barre de recherche ne reprenne automatiquement le focus // lorsque l'utilisateur interagit avec un autre champ de texte searchInput.addEventListener('blur', (e) => { // Ne pas reprendre le focus si l'utilisateur clique sur un autre input const newFocusElement = e.relatedTarget; if (newFocusElement && (newFocusElement.tagName === 'INPUT' || newFocusElement.tagName === 'TEXTAREA')) { // L'utilisateur a cliqué sur un autre champ de texte, ne pas reprendre le focus return; } // Pour les autres cas, seulement si aucun autre élément n'a le focus setTimeout(() => { const activeElement = document.activeElement; if (this.isClientMenuVisible && activeElement && activeElement !== searchInput && activeElement.tagName !== 'INPUT' && activeElement.tagName !== 'TEXTAREA') { searchInput.focus(); } }, 100); }); } this.menu.appendChild(header); } clearMenu() { const gridContainer = document.getElementById('kxsMenuGrid'); if (gridContainer) { gridContainer.innerHTML = ''; } // Reset search term when clearing menu this.searchTerm = ''; const searchInput = document.getElementById('kxsSearchInput'); if (searchInput) { searchInput.value = ''; } } loadOption() { // Clear existing options to avoid duplicates this.allOptions = []; let HUD = this.addSection("HUD", 'HUD'); let MECHANIC = this.addSection("MECHANIC", 'MECHANIC'); let SERVER = this.addSection("SERVER", 'SERVER'); let SOUND = this.addSection("SOUND", 'SOUND'); let MISC = this.addSection("MISC", 'MISC'); this.addOption(MISC, { label: "Game History", value: true, category: "MISC", icon: ' ', type: "click", onChange: () => { this.kxsClient.historyManager.show(); } }); this.addOption(SOUND, { label: "Win sound", value: this.kxsClient.soundLibrary.win_sound_url, category: "SOUND", icon: ' ', type: "input", placeholder: "URL of a sound", onChange: (value) => { this.kxsClient.soundLibrary.win_sound_url = value; this.kxsClient.updateLocalStorage(); } }); this.addOption(SOUND, { label: "Death sound", value: this.kxsClient.soundLibrary.death_sound_url, category: "SOUND", icon: ' ', type: "input", placeholder: "URL of a sound", onChange: (value) => { this.kxsClient.soundLibrary.death_sound_url = value; this.kxsClient.updateLocalStorage(); } }); this.addOption(HUD, { label: "Clean Main Menu", value: this.kxsClient.isMainMenuCleaned, category: "HUD", icon: ' clean ', type: "toggle", onChange: (value) => { this.kxsClient.isMainMenuCleaned = !this.kxsClient.isMainMenuCleaned; this.kxsClient.MainMenuCleaning(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show Ping", value: this.kxsClient.isPingVisible, category: "HUD", icon: '', type: "toggle", onChange: (value) => { this.kxsClient.isPingVisible = !this.kxsClient.isPingVisible; this.kxsClient.updatePingVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show FPS", value: this.kxsClient.isFpsVisible, category: "HUD", type: "toggle", icon: '', onChange: (value) => { this.kxsClient.isFpsVisible = !this.kxsClient.isFpsVisible; this.kxsClient.updateFpsVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Show Kills", value: this.kxsClient.isKillsVisible, type: "toggle", category: "HUD", icon: ' ', onChange: (value) => { this.kxsClient.isKillsVisible = !this.kxsClient.isKillsVisible; this.kxsClient.updateKillsVisibility(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Weapon Border", value: this.kxsClient.isGunOverlayColored, category: "HUD", type: "toggle", icon: ' ', onChange: (value) => { this.kxsClient.isGunOverlayColored = !this.kxsClient.isGunOverlayColored; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleWeaponBorderHandler(); }, }); this.addOption(HUD, { label: "Chromatic Weapon Border", value: this.kxsClient.isGunBorderChromatic, category: "HUD", type: "toggle", icon: ' ', onChange: (value) => { this.kxsClient.isGunBorderChromatic = !this.kxsClient.isGunBorderChromatic; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleChromaticWeaponBorder(); }, }); this.addOption(HUD, { label: "Focus Mode", value: "Hold Left CTRL for 1 seconds to toggle Focus Mode.\nWhen enabled, the HUD will dim and notifications will appear.", category: "HUD", type: "info", icon: ' ', onChange: () => { } }); this.addOption(HUD, { label: "Use Legacy Menu", value: this.kxsClient.isLegaySecondaryMenu, type: "toggle", category: 'HUD', icon: ' ', onChange: (value) => { this.kxsClient.isLegaySecondaryMenu = !this.kxsClient.isLegaySecondaryMenu; this.kxsClient.updateLocalStorage(); this.kxsClient.secondaryMenu = new KxsLegacyClientSecondaryMenu(this.kxsClient); this.destroy(); }, }); this.addOption(MECHANIC, { label: "Death sound", value: this.kxsClient.isDeathSoundEnabled, type: "toggle", icon: ' ', category: "MECHANIC", onChange: (value) => { this.kxsClient.isDeathSoundEnabled = !this.kxsClient.isDeathSoundEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Win sound", value: this.kxsClient.isWinSoundEnabled, type: "toggle", icon: ' ', category: "HUD", onChange: (value) => { this.kxsClient.isWinSoundEnabled = !this.kxsClient.isWinSoundEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: "Message Open/Close RSHIFT Menu", value: this.kxsClient.isNotifyingForToggleMenu, type: "toggle", icon: ' ', category: "HUD", onChange: (value) => { this.kxsClient.isNotifyingForToggleMenu = !this.kxsClient.isNotifyingForToggleMenu; this.kxsClient.updateLocalStorage(); }, }); this.addOption(SERVER, { label: "Webhook URL", value: this.kxsClient.discordWebhookUrl || "", icon: ' ', category: "SERVER", type: "input", placeholder: "discord webhook url", onChange: (value) => { value = value.toString().trim(); this.kxsClient.discordWebhookUrl = value; this.kxsClient.discordTracker.setWebhookUrl(value); this.kxsClient.updateLocalStorage(); }, }); this.addOption(MECHANIC, { label: "Custom Crosshair", value: this.kxsClient.customCrosshair || "", type: "input", category: "MECHANIC", icon: ' crosshair ', placeholder: "URL of png,gif,svg", onChange: (value) => { this.kxsClient.customCrosshair = value; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.loadCustomCrosshair(); }, }); this.addOption(MECHANIC, { label: "Heal Warning", value: this.kxsClient.isHealthWarningEnabled, type: "toggle", category: "MECHANIC", icon: ' health ', onChange: (value) => { var _a, _b; this.kxsClient.isHealthWarningEnabled = !this.kxsClient.isHealthWarningEnabled; if (this.kxsClient.isHealthWarningEnabled) { // Always enter placement mode when enabling from RSHIFT menu (_a = this.kxsClient.healWarning) === null || _a === void 0 ? void 0 : _a.enableDragging(); } else { (_b = this.kxsClient.healWarning) === null || _b === void 0 ? void 0 : _b.hide(); } this.kxsClient.updateLocalStorage(); }, }); this.addOption(SERVER, { label: "Update Checker", value: this.kxsClient.isAutoUpdateEnabled, type: "toggle", icon: ' ', category: "SERVER", onChange: (value) => { this.kxsClient.isAutoUpdateEnabled = !this.kxsClient.isAutoUpdateEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(MECHANIC, { label: `Uncap FPS`, value: this.kxsClient.isFpsUncapped, type: "toggle", icon: ' ic_fluent_fps_960_24_filled Created with Sketch. ', category: 'MECHANIC', onChange: () => { this.kxsClient.isFpsUncapped = !this.kxsClient.isFpsUncapped; this.kxsClient.setAnimationFrameCallback(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: `Winning Animation`, value: this.kxsClient.isWinningAnimationEnabled, icon: ' ', category: "HUD", type: "toggle", onChange: () => { this.kxsClient.isWinningAnimationEnabled = !this.kxsClient.isWinningAnimationEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: `Spotify Player`, value: this.kxsClient.isSpotifyPlayerEnabled, icon: ' spotify ', category: "HUD", type: "toggle", onChange: () => { this.kxsClient.isSpotifyPlayerEnabled = !this.kxsClient.isSpotifyPlayerEnabled; this.kxsClient.updateLocalStorage(); this.kxsClient.toggleSpotifyMenu(); }, }); this.addOption(HUD, { label: "Kill Feed Chroma", value: this.kxsClient.isKillFeedBlint, icon: ` `, category: "HUD", type: "toggle", onChange: () => { this.kxsClient.isKillFeedBlint = !this.kxsClient.isKillFeedBlint; this.kxsClient.updateLocalStorage(); this.kxsClient.hud.toggleKillFeed(); }, }); this.addOption(SERVER, { label: `Rich Presence (Account token required)`, value: this.kxsClient.discordToken || "", icon: ' ', category: "SERVER", type: "input", placeholder: "Your discord account token", onChange: (value) => { value = value.toString().trim(); this.kxsClient.discordToken = this.kxsClient.parseToken(value); this.kxsClient.discordRPC.disconnect(); this.kxsClient.discordRPC.connect(); this.kxsClient.updateLocalStorage(); }, }); this.addOption(MECHANIC, { label: `Kill Leader Tracking`, icon: ' crown ', category: "MECHANIC", value: this.kxsClient.isKillLeaderTrackerEnabled, type: "toggle", onChange: (value) => { this.kxsClient.isKillLeaderTrackerEnabled = !this.kxsClient.isKillLeaderTrackerEnabled; this.kxsClient.updateLocalStorage(); }, }); this.addOption(MECHANIC, { label: `Friends Detector (separe with ',')`, icon: ' ', category: "MECHANIC", value: this.kxsClient.all_friends, type: "input", placeholder: "kisakay,iletal...", onChange: (value) => { this.kxsClient.all_friends = value; this.kxsClient.updateLocalStorage(); }, }); this.addOption(HUD, { label: `Change Background`, icon: ' ', category: "HUD", value: true, type: "click", onChange: () => { const backgroundElement = document.getElementById("background"); if (!backgroundElement) { alert("Element with id 'background' not found."); return; } const choice = prompt("Enter '0' to default Kxs background, '1' to provide a URL or '2' to upload a local image:"); if (choice === "0") { localStorage.removeItem("lastBackgroundUrl"); localStorage.removeItem("lastBackgroundFile"); localStorage.removeItem("lastBackgroundType"); localStorage.removeItem("lastBackgroundValue"); backgroundElement.style.backgroundImage = `url(${background_image})`; } else if (choice === "1") { const newBackgroundUrl = prompt("Enter the URL of the new background image:"); if (newBackgroundUrl) { backgroundElement.style.backgroundImage = `url(${newBackgroundUrl})`; this.kxsClient.saveBackgroundToLocalStorage(newBackgroundUrl); alert("Background updated successfully!"); } } else if (choice === "2") { const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.accept = "image/*"; fileInput.onchange = (event) => { var _a, _b; const file = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]; if (file) { const reader = new FileReader(); reader.onload = () => { backgroundElement.style.backgroundImage = `url(${reader.result})`; this.kxsClient.saveBackgroundToLocalStorage(file); alert("Background updated successfully!"); }; reader.readAsDataURL(file); } }; fileInput.click(); } }, }); } createOptionCard(option, container) { const optionCard = document.createElement("div"); Object.assign(optionCard.style, { background: "rgba(31, 41, 55, 0.8)", borderRadius: "10px", padding: "16px", display: "flex", flexDirection: "column", alignItems: "center", gap: "12px", minHeight: "150px", }); const iconContainer = document.createElement("div"); Object.assign(iconContainer.style, { width: "48px", height: "48px", borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: "8px" }); iconContainer.innerHTML = option.icon || ''; const title = document.createElement("div"); title.textContent = option.label; title.style.fontSize = "16px"; title.style.textAlign = "center"; let control = null; switch (option.type) { case "info": control = this.createInfoElement(option); break; case "input": control = this.createInputElement(option); break; case "toggle": control = this.createToggleButton(option); break; case "click": control = this.createClickButton(option); } optionCard.appendChild(iconContainer); optionCard.appendChild(title); optionCard.appendChild(control); container.appendChild(optionCard); } setActiveCategory(category) { this.activeCategory = category; this.filterOptions(); // Update button styles this.menu.querySelectorAll('.category-btn').forEach(btn => { const btnCategory = btn.dataset.category; btn.style.background = btnCategory === category ? '#3B82F6' : 'rgba(55, 65, 81, 0.8)'; }); } filterOptions() { const gridContainer = document.getElementById('kxsMenuGrid'); if (gridContainer) { // Clear existing content gridContainer.innerHTML = ''; // Get unique options based on category and search term const displayedOptions = new Set(); this.sections.forEach(section => { if (this.activeCategory === 'ALL' || section.category === this.activeCategory) { section.options.forEach(option => { // Create a unique key for each option const optionKey = `${option.label}-${option.category}`; // Check if option matches search term const matchesSearch = this.searchTerm === '' || option.label.toLowerCase().includes(this.searchTerm) || option.category.toLowerCase().includes(this.searchTerm); if (!displayedOptions.has(optionKey) && matchesSearch) { displayedOptions.add(optionKey); this.createOptionCard(option, gridContainer); } }); } }); // Show a message if no options match the search if (displayedOptions.size === 0 && this.searchTerm !== '') { const noResultsMsg = document.createElement('div'); noResultsMsg.textContent = `No results found for "${this.searchTerm}"`; noResultsMsg.style.gridColumn = '1 / -1'; noResultsMsg.style.textAlign = 'center'; noResultsMsg.style.padding = '20px'; noResultsMsg.style.color = '#9CA3AF'; gridContainer.appendChild(noResultsMsg); } } } createGridContainer() { const gridContainer = document.createElement("div"); const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile(); Object.assign(gridContainer.style, { display: "grid", gridTemplateColumns: isMobile ? "repeat(4, 1fr)" : "repeat(3, 1fr)", gap: isMobile ? "5px" : "16px", padding: isMobile ? "2px" : "16px", gridAutoRows: isMobile ? "minmax(38px, auto)" : "minmax(150px, auto)", overflowY: "auto", overflowX: "hidden", // Prevent horizontal scrolling maxHeight: isMobile ? "28vh" : "calc(3 * 150px + 2 * 16px)", width: "100%", boxSizing: "border-box" // Include padding in width calculation }); gridContainer.id = "kxsMenuGrid"; this.menu.appendChild(gridContainer); } addOption(section, option) { section.options.push(option); // Store all options for searching this.allOptions.push(option); } addSection(title, category = "ALL") { const section = { title, options: [], category }; const sectionElement = document.createElement("div"); sectionElement.className = "menu-section"; sectionElement.style.display = this.activeCategory === "ALL" || this.activeCategory === category ? "block" : "none"; section.element = sectionElement; this.sections.push(section); this.menu.appendChild(sectionElement); return section; } createToggleButton(option) { const btn = document.createElement("button"); const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile(); Object.assign(btn.style, { width: "100%", padding: isMobile ? "2px 0px" : "8px", height: isMobile ? "24px" : "auto", background: option.value ? "#059669" : "#DC2626", border: "none", borderRadius: isMobile ? "3px" : "6px", color: "white", cursor: "pointer", transition: "background 0.2s", fontSize: isMobile ? "9px" : "14px", fontWeight: "bold", minHeight: isMobile ? "20px" : "unset", letterSpacing: isMobile ? "0.5px" : "1px" }); btn.textContent = option.value ? "ENABLED" : "DISABLED"; btn.addEventListener("click", () => { var _a; const newValue = !option.value; option.value = newValue; btn.textContent = newValue ? "ENABLED" : "DISABLED"; btn.style.background = newValue ? "#059669" : "#DC2626"; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, newValue); }); this.blockMousePropagation(btn); return btn; } createClickButton(option) { const btn = document.createElement("button"); const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile(); Object.assign(btn.style, { width: "100%", padding: isMobile ? "2px 0px" : "8px", height: isMobile ? "24px" : "auto", background: "#3B82F6", border: "none", borderRadius: "6px", color: "white", cursor: "pointer", transition: "background 0.2s", fontSize: "14px", fontWeight: "bold" }); btn.textContent = option.label; btn.addEventListener("click", () => { var _a; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, true); }); this.blockMousePropagation(btn); return btn; } addShiftListener() { // Gestionnaire pour la touche Shift (ouverture du menu) window.addEventListener("keydown", (event) => { if (event.key === "Shift" && event.location == 2) { this.clearMenu(); this.toggleMenuVisibility(); // Ensure options are displayed after loading this.filterOptions(); } }); // Gestionnaire séparé pour la touche Échap avec capture en phase de capture // pour intercepter l'événement avant qu'il n'atteigne le jeu document.addEventListener("keydown", (event) => { if (event.key === "Escape" && this.isClientMenuVisible) { // Fermer le menu si la touche Échap est pressée et que le menu est visible this.toggleMenuVisibility(); // Empêcher la propagation ET l'action par défaut event.stopPropagation(); event.preventDefault(); // Arrêter la propagation de l'événement return false; } }, true); // true = phase de capture } createInputElement(option) { const input = document.createElement("input"); input.type = "text"; input.value = String(option.value); if (option.placeholder) { input.placeholder = option.placeholder; } Object.assign(input.style, { width: "100%", padding: "8px", background: "rgba(55, 65, 81, 0.8)", border: "none", borderRadius: "6px", color: "#FFAE00", fontSize: "14px" }); input.addEventListener("change", () => { var _a; option.value = input.value; (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, input.value); }); // Empêcher la propagation des touches de texte vers la page web // mais permettre l'interaction avec l'input input.addEventListener("keydown", (e) => { // Ne pas arrêter la propagation des touches de navigation (flèches, tab, etc.) // qui sont nécessaires pour naviguer dans le champ de texte e.stopPropagation(); }); input.addEventListener("keyup", (e) => { e.stopPropagation(); }); input.addEventListener("keypress", (e) => { e.stopPropagation(); }); this.blockMousePropagation(input); return input; } createInfoElement(option) { const info = document.createElement("div"); info.textContent = String(option.value); Object.assign(info.style, { color: "#b0b0b0", fontSize: "12px", fontStyle: "italic", marginTop: "2px", marginLeft: "6px", marginBottom: "2px", flex: "1 1 100%", whiteSpace: "pre-line" }); this.blockMousePropagation(info); return info; } addDragListeners() { this.menu.addEventListener('mousedown', (e) => { // Ne pas arrêter la propagation si l'événement vient d'un élément interactif if (e.target instanceof HTMLElement && e.target.matches("input, select, button, svg, path")) { // Laisser l'événement se propager aux éléments interactifs return; } // Empêcher la propagation de l'événement mousedown vers la page web e.stopPropagation(); // Activer le drag & drop seulement si on clique sur une zone non interactive if (e.target instanceof HTMLElement && !e.target.matches("input, select, button, svg, path")) { this.isDragging = true; const rect = this.menu.getBoundingClientRect(); this.dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top }; this.menu.style.cursor = "grabbing"; } }); document.addEventListener('mousemove', (e) => { if (this.isDragging) { const x = e.clientX - this.dragOffset.x; const y = e.clientY - this.dragOffset.y; this.menu.style.transform = 'none'; this.menu.style.left = `${x}px`; this.menu.style.top = `${y}px`; } }); document.addEventListener('mouseup', (e) => { // Arrêter le drag & drop const wasDragging = this.isDragging; this.isDragging = false; this.menu.style.cursor = "grab"; // Empêcher la propagation de l'événement mouseup vers la page web // seulement si l'événement vient du menu et n'est pas un élément interactif if (this.menu.contains(e.target)) { if (wasDragging || !(e.target instanceof HTMLElement && e.target.matches("input, select, button, svg, path"))) { e.stopPropagation(); } } }); } toggleMenuVisibility() { this.isClientMenuVisible = !this.isClientMenuVisible; // Mettre à jour la propriété publique en même temps this.isOpen = this.isClientMenuVisible; if (this.kxsClient.isNotifyingForToggleMenu) { this.kxsClient.nm.showNotification(this.isClientMenuVisible ? "Opening menu..." : "Closing menu...", "info", 1400); } this.menu.style.display = this.isClientMenuVisible ? "block" : "none"; // If opening the menu, make sure to display options if (this.isClientMenuVisible) { this.filterOptions(); } } destroy() { // Remove global event listeners window.removeEventListener("keydown", this.shiftListener); document.removeEventListener('mousemove', this.mouseMoveListener); document.removeEventListener('mouseup', this.mouseUpListener); // Supprimer tous les écouteurs d'événements keydown du document // Nous ne pouvons pas supprimer directement l'écouteur anonyme, mais ce n'est pas grave // car la vérification isClientMenuVisible empêchera toute action une fois le menu détruit // Remove all event listeners from menu elements const removeAllListeners = (element) => { var _a; const clone = element.cloneNode(true); (_a = element.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(clone, element); }; // Clean up all buttons and inputs in the menu this.menu.querySelectorAll('button, input').forEach(element => { removeAllListeners(element); }); // Remove the menu from DOM this.menu.remove(); // Clear all sections this.sections.forEach(section => { if (section.element) { removeAllListeners(section.element); section.element.remove(); delete section.element; } section.options = []; }); this.sections = []; // Reset all class properties this.isClientMenuVisible = false; this.isDragging = false; this.dragOffset = { x: 0, y: 0 }; this.activeCategory = "ALL"; // Clear references this.menu = null; this.kxsClient = null; } getMenuVisibility() { return this.isClientMenuVisible; } } ;// ./src/SERVER/Ping.ts class PingTest { constructor() { this.ping = 0; this.ws = null; this.sendTime = 0; this.retryCount = 0; this.isConnecting = false; this.isWebSocket = true; this.url = ""; this.region = ""; this.hasPing = false; this.reconnectTimer = null; this.keepAliveTimer = null; this.connectionCheckTimer = null; this.ptcDataBuf = new ArrayBuffer(1); this.waitForServerSelectElements(); this.startKeepAlive(); } startKeepAlive() { // Annuler l'ancien timer si existant if (this.keepAliveTimer) { clearInterval(this.keepAliveTimer); } this.keepAliveTimer = setInterval(() => { var _a, _b, _c; if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { this.ws.send(this.ptcDataBuf); } else if (((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) === WebSocket.CLOSED || ((_c = this.ws) === null || _c === void 0 ? void 0 : _c.readyState) === WebSocket.CLOSING) { // Redémarrer la connexion si elle est fermée this.restart(); } }, 5000); // envoie toutes les 5s } waitForServerSelectElements() { const checkInterval = setInterval(() => { const teamSelect = document.getElementById("team-server-select"); const mainSelect = document.getElementById("server-select-main"); const selectedValue = (teamSelect === null || teamSelect === void 0 ? void 0 : teamSelect.value) || (mainSelect === null || mainSelect === void 0 ? void 0 : mainSelect.value); if ((teamSelect || mainSelect) && selectedValue) { clearInterval(checkInterval); this.setServerFromDOM(); this.attachRegionChangeListener(); } }, 100); // Vérifie toutes les 100ms } setServerFromDOM() { const { region, url } = this.detectSelectedServer(); this.region = region; this.url = `wss://${url}/ptc`; this.start(); } detectSelectedServer() { const currentUrl = window.location.href; const isSpecialUrl = /\/#\w+/.test(currentUrl); const teamSelectElement = document.getElementById("team-server-select"); const mainSelectElement = document.getElementById("server-select-main"); const region = isSpecialUrl && teamSelectElement ? teamSelectElement.value : (mainSelectElement === null || mainSelectElement === void 0 ? void 0 : mainSelectElement.value) || "NA"; const servers = [ { region: "NA", url: "usr.mathsiscoolfun.com:8001" }, { region: "EU", url: "eur.mathsiscoolfun.com:8001" }, { region: "Asia", url: "asr.mathsiscoolfun.com:8001" }, { region: "SA", url: "sa.mathsiscoolfun.com:8001" }, ]; const selectedServer = servers.find((s) => s.region.toUpperCase() === region.toUpperCase()); if (!selectedServer) throw new Error("Aucun serveur correspondant trouvé"); return selectedServer; } attachRegionChangeListener() { const teamSelectElement = document.getElementById("team-server-select"); const mainSelectElement = document.getElementById("server-select-main"); const onChange = () => { const { region } = this.detectSelectedServer(); if (region !== this.region) { this.restart(); } }; teamSelectElement === null || teamSelectElement === void 0 ? void 0 : teamSelectElement.addEventListener("change", onChange); mainSelectElement === null || mainSelectElement === void 0 ? void 0 : mainSelectElement.addEventListener("change", onChange); } start() { if (this.isConnecting) return; this.isConnecting = true; this.startWebSocketPing(); // Vérifier régulièrement l'état de la connexion this.startConnectionCheck(); } startConnectionCheck() { // Annuler l'ancien timer si existant if (this.connectionCheckTimer) { clearInterval(this.connectionCheckTimer); } // Vérifier l'état de la connexion toutes les 10 secondes this.connectionCheckTimer = setInterval(() => { // Si on n'a pas de ping valide ou que la connexion est fermée, on tente de reconnecter if (!this.hasPing || !this.ws || this.ws.readyState !== WebSocket.OPEN) { this.restart(); } }, 10000); } startWebSocketPing() { if (this.ws || !this.url) return; const ws = new WebSocket(this.url); ws.binaryType = "arraybuffer"; ws.onopen = () => { this.ws = ws; this.retryCount = 0; this.isConnecting = false; this.sendPing(); setTimeout(() => { var _a; if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) !== WebSocket.OPEN) { this.restart(); } }, 3000); // 3s pour sécuriser }; ws.onmessage = () => { this.hasPing = true; const elapsed = (Date.now() - this.sendTime) / 1e3; this.ping = Math.round(elapsed * 1000); setTimeout(() => this.sendPing(), 1000); }; ws.onerror = (error) => { this.ping = 0; this.hasPing = false; this.retryCount++; // Tentative immédiate mais avec backoff exponentiel const retryDelay = Math.min(1000 * Math.pow(2, this.retryCount - 1), 10000); // Annuler tout timer de reconnexion existant if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } this.reconnectTimer = setTimeout(() => { this.ws = null; // S'assurer que l'ancienne connexion est effacée this.startWebSocketPing(); }, retryDelay); }; ws.onclose = (event) => { this.hasPing = false; this.ws = null; this.isConnecting = false; // Tentative de reconnexion après une fermeture if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } this.reconnectTimer = setTimeout(() => { this.start(); }, 2000); // Attendre 2 secondes avant de reconnecter }; } sendPing() { var _a, _b; if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.sendTime = Date.now(); this.ws.send(this.ptcDataBuf); } else if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.CLOSED || ((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) === WebSocket.CLOSING) { // Si la WebSocket est fermée au moment d'envoyer le ping, on tente de reconnecter this.restart(); } } stop() { // Annuler tous les timers if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.keepAliveTimer) { clearInterval(this.keepAliveTimer); this.keepAliveTimer = null; } if (this.connectionCheckTimer) { clearInterval(this.connectionCheckTimer); this.connectionCheckTimer = null; } if (this.ws) { this.ws.onclose = null; this.ws.onerror = null; this.ws.onmessage = null; this.ws.onopen = null; this.ws.close(); this.ws = null; } this.isConnecting = false; this.retryCount = 0; this.hasPing = false; } restart() { this.stop(); setTimeout(() => { this.setServerFromDOM(); }, 500); // Petit délai pour éviter les problèmes de rebond } /** * Retourne le ping actuel. Ne touche jamais à la websocket ici ! * Si le ping n'est pas dispo, retourne -1 (jamais null). * La reconnexion doit être gérée ailleurs (timer, event, etc). */ getPingResult() { if (this.ws && this.ws.readyState === WebSocket.OPEN && this.hasPing) { return { region: this.region, ping: this.ping, }; } else { // Si on détecte un problème ici, planifier une reconnexion if (!this.reconnectTimer && (!this.ws || this.ws.readyState !== WebSocket.CONNECTING)) { this.reconnectTimer = setTimeout(() => this.restart(), 1000); } return { region: this.region, ping: -1, // -1 indique que le ping n'est pas dispo, mais jamais null }; } } } ;// ./src/HUD/ClientHUD.ts class KxsClientHUD { constructor(kxsClient) { this.healthAnimations = []; this.lastHealthValue = 100; this.hudOpacityObservers = []; this.weaponBorderObservers = []; this.ctrlFocusTimer = null; this.killFeedObserver = null; this.kxsClient = kxsClient; this.frameCount = 0; this.fps = 0; this.kills = 0; this.isMenuVisible = true; this.pingManager = new PingTest(); this.allDivToHide = [ '#ui-medical-interactive > div', '#ui-ammo-interactive > div', '#ui-weapon-container .ui-weapon-switch', '#ui-killfeed', '#ui-killfeed-contents', '.killfeed-div', '.killfeed-text', '#ui-kill-leader-container', '#ui-kill-leader-wrapper', '#ui-kill-leader-name', '#ui-kill-leader-icon', '#ui-kill-leader-count', '#ui-leaderboard-wrapper', '#ui-leaderboard', '#ui-leaderboard-alive', '#ui-leaderboard-alive-faction', '.ui-leaderboard-header', '#ui-kill-counter-wrapper', '#ui-kill-counter', '.ui-player-kills', '.ui-kill-counter-header', '#ui-bottom-center-right', '#ui-armor-helmet', '#ui-armor-chest', '#ui-armor-backpack', '.ui-armor-counter', '.ui-armor-counter-inner', '.ui-armor-level', '.ui-armor-image', '.ui-loot-image', ]; if (this.kxsClient.isPingVisible) { this.initCounter("ping", "Ping", "45ms"); } if (this.kxsClient.isFpsVisible) { this.initCounter("fps", "FPS", "60"); } if (this.kxsClient.isKillsVisible) { this.initCounter("kills", "Kills", "0"); } if (this.kxsClient.isGunOverlayColored) { this.toggleWeaponBorderHandler(); } this.startUpdateLoop(); this.escapeMenu(); this.initFriendDetector(); if (this.kxsClient.isKillFeedBlint) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', this.initKillFeed); } else { this.initKillFeed(); } } if (this.kxsClient.customCrosshair !== null) { this.loadCustomCrosshair(); } this.setupCtrlFocusModeListener(); } setupCtrlFocusModeListener() { document.addEventListener('keydown', (e) => { if (e.code === 'ControlLeft' && !this.ctrlFocusTimer) { this.ctrlFocusTimer = window.setTimeout(() => { this.kxsClient.isFocusModeEnabled = !this.kxsClient.isFocusModeEnabled; this.kxsClient.hud.toggleFocusMode(); this.kxsClient.nm.showNotification("Focus mode toggled", "info", 1200); }, 1000); } }); document.addEventListener('keyup', (e) => { if (e.code === 'ControlLeft' && this.ctrlFocusTimer) { clearTimeout(this.ctrlFocusTimer); this.ctrlFocusTimer = null; } }); } initFriendDetector() { // Initialize friends list let all_friends = this.kxsClient.all_friends.split(',') || []; if (all_friends.length >= 1) { // Create a cache for detected friends // Structure will be: { "friendName": timestamp } const friendsCache = {}; // Cache duration in milliseconds (4 minutes = 240000 ms) const cacheDuration = 4 * 60 * 1000; // Select the element containing kill feeds const killfeedContents = document.querySelector('#ui-killfeed-contents'); if (killfeedContents) { // Keep track of last seen content for each div const lastSeenContent = { "ui-killfeed-0": "", "ui-killfeed-1": "", "ui-killfeed-2": "", "ui-killfeed-3": "", "ui-killfeed-4": "", "ui-killfeed-5": "" }; // Function to check if a friend is in the text with cache management const checkForFriends = (text, divId) => { // If the text is identical to the last seen, ignore // @ts-ignore if (text === lastSeenContent[divId]) return; // Update the last seen content // @ts-ignore lastSeenContent[divId] = text; // Ignore empty messages if (!text.trim()) return; // Current timestamp const currentTime = Date.now(); // Check if a friend is mentioned for (let friend of all_friends) { if (friend !== "" && text.includes(friend)) { // Check if the friend is in the cache and if the cache is still valid // @ts-ignore const lastSeen = friendsCache[friend]; if (!lastSeen || (currentTime - lastSeen > cacheDuration)) { // Update the cache // @ts-ignore friendsCache[friend] = currentTime; // Display notification this.kxsClient.nm.showNotification(`[FriendDetector] ${friend} is in this game`, "info", 2300); } break; } } }; // Function to check all kill feeds const checkAllKillfeeds = () => { all_friends = this.kxsClient.all_friends.split(',') || []; for (let i = 0; i <= 5; i++) { const divId = `ui-killfeed-${i}`; const killDiv = document.getElementById(divId); if (killDiv) { const textElement = killDiv.querySelector('.killfeed-text'); if (textElement && textElement.textContent) { checkForFriends(textElement.textContent, divId); } } } }; // Observe style or text changes in the entire container const observer = new MutationObserver(() => { checkAllKillfeeds(); }); // Start observing with a configuration that detects all changes observer.observe(killfeedContents, { childList: true, // Observe changes to child elements subtree: true, // Observe the entire tree characterData: true, // Observe text changes attributes: true // Observe attribute changes (like style/opacity) }); // Check current content immediately checkAllKillfeeds(); } else { this.kxsClient.logger.error("Killfeed-contents element not found"); } } } initKillFeed() { this.applyCustomStyles(); this.setupObserver(); } toggleKillFeed() { if (this.kxsClient.isKillFeedBlint) { this.initKillFeed(); // <-- injecte le CSS custom et observer } else { this.resetKillFeed(); // <-- supprime styles et contenu } } /** * Réinitialise le Kill Feed à l'état par défaut (vide) */ /** * Supprime tous les styles custom KillFeed injectés par applyCustomStyles */ resetKillFeedStyles() { // Supprime tous les