mirror of
https://github.com/TheAlgorithms/JavaScript.git
synced 2026-03-13 15:21:15 +08:00
feat: Test running overhaul, switch to Prettier & reformat everything (#1407)
* chore: Switch to Node 20 + Vitest * chore: migrate to vitest mock functions * chore: code style (switch to prettier) * test: re-enable long-running test Seems the switch to Node 20 and Vitest has vastly improved the code's and / or the test's runtime! see #1193 * chore: code style * chore: fix failing tests * Updated Documentation in README.md * Update contribution guidelines to state usage of Prettier * fix: set prettier printWidth back to 80 * chore: apply updated code style automatically * fix: set prettier line endings to lf again * chore: apply updated code style automatically --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,7 @@ const key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
* @param {Number} m - Modulos value
|
||||
* @return {Number} Return n mod m
|
||||
*/
|
||||
function mod (n, m) {
|
||||
function mod(n, m) {
|
||||
return ((n % m) + m) % m
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ function mod (n, m) {
|
||||
* @param {Number} m - Modulos value
|
||||
* @return {Number} Return modular multiplicative inverse of coefficient a and modulos m
|
||||
*/
|
||||
function inverseMod (a, m) {
|
||||
function inverseMod(a, m) {
|
||||
for (let x = 1; x < m; x++) {
|
||||
if (mod(a * x, m) === 1) return x
|
||||
}
|
||||
@@ -36,7 +36,7 @@ function inverseMod (a, m) {
|
||||
* @param {Number} b - B coefficient to be checked
|
||||
* @return {Boolean} Result of the checking
|
||||
*/
|
||||
function isCorrectFormat (str, a, b) {
|
||||
function isCorrectFormat(str, a, b) {
|
||||
if (typeof a !== 'number' || typeof b !== 'number') {
|
||||
throw new TypeError('Coefficient a, b should be number')
|
||||
}
|
||||
@@ -57,8 +57,8 @@ function isCorrectFormat (str, a, b) {
|
||||
* @param {String} char - Character index to be found
|
||||
* @return {Boolean} Character index
|
||||
*/
|
||||
function findCharIndex (char) {
|
||||
return char.toUpperCase().charCodeAt(0) - ('A').charCodeAt(0)
|
||||
function findCharIndex(char) {
|
||||
return char.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +69,7 @@ function findCharIndex (char) {
|
||||
* @return {String} result - Encrypted string
|
||||
*/
|
||||
|
||||
function encrypt (str, a, b) {
|
||||
function encrypt(str, a, b) {
|
||||
let result = ''
|
||||
if (isCorrectFormat(str, a, b)) {
|
||||
for (let x = 0; x < str.length; x++) {
|
||||
@@ -88,7 +88,7 @@ function encrypt (str, a, b) {
|
||||
* @param {Number} b - B coefficient
|
||||
* @return {String} result - Decrypted string
|
||||
*/
|
||||
function decrypt (str, a, b) {
|
||||
function decrypt(str, a, b) {
|
||||
let result = ''
|
||||
if (isCorrectFormat(str, a, b)) {
|
||||
str = str.split(' ')
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
Find and retrieve the encryption key automatically
|
||||
Note: This is a draft version, please help to modify, Thanks!
|
||||
******************************************************/
|
||||
function keyFinder (str) { // str is used to get the input of encrypted string
|
||||
function keyFinder(str) {
|
||||
// str is used to get the input of encrypted string
|
||||
const wordBank = [
|
||||
'I ',
|
||||
'You ',
|
||||
@@ -27,13 +28,15 @@ function keyFinder (str) { // str is used to get the input of encrypted string
|
||||
' may ',
|
||||
'May ',
|
||||
' be ',
|
||||
'Be ']
|
||||
'Be '
|
||||
]
|
||||
// let wordbankelementCounter = 0;
|
||||
// let key = 0; // return zero means the key can not be found
|
||||
const inStr = str.toString() // convert the input to String
|
||||
let outStr = '' // store the output value
|
||||
let outStrElement = '' // temporary store the word inside the outStr, it is used for comparison
|
||||
for (let k = 0; k < 26; k++) { // try the number of key shifted, the sum of character from a-z or A-Z is 26
|
||||
for (let k = 0; k < 26; k++) {
|
||||
// try the number of key shifted, the sum of character from a-z or A-Z is 26
|
||||
outStr = caesarCipherEncodeAndDecodeEngine(inStr, k) // use the encryption engine to decrypt the input string
|
||||
|
||||
// loop through the whole input string
|
||||
@@ -57,7 +60,7 @@ function keyFinder (str) { // str is used to get the input of encrypted string
|
||||
}
|
||||
|
||||
/* this sub-function is used to assist the keyFinder to find the key */
|
||||
function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) {
|
||||
function caesarCipherEncodeAndDecodeEngine(inStr, numShifted) {
|
||||
const shiftNum = numShifted
|
||||
let charCode = 0
|
||||
let outStr = ''
|
||||
@@ -69,7 +72,7 @@ function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) {
|
||||
shiftedCharCode = charCode + shiftNum
|
||||
result = charCode
|
||||
|
||||
if ((charCode >= 48 && charCode <= 57)) {
|
||||
if (charCode >= 48 && charCode <= 57) {
|
||||
if (shiftedCharCode < 48) {
|
||||
let diff = Math.abs(48 - 1 - shiftedCharCode) % 10
|
||||
|
||||
@@ -95,11 +98,11 @@ function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) {
|
||||
|
||||
result = shiftedCharCode
|
||||
}
|
||||
} else if ((charCode >= 65 && charCode <= 90)) {
|
||||
} else if (charCode >= 65 && charCode <= 90) {
|
||||
if (shiftedCharCode <= 64) {
|
||||
let diff = Math.abs(65 - 1 - shiftedCharCode) % 26
|
||||
|
||||
while ((diff % 26) >= 26) {
|
||||
while (diff % 26 >= 26) {
|
||||
diff = diff % 26
|
||||
}
|
||||
shiftedCharCode = 90 - diff
|
||||
@@ -109,17 +112,17 @@ function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) {
|
||||
} else if (shiftedCharCode > 90) {
|
||||
let diff = Math.abs(shiftedCharCode - 1 - 90) % 26
|
||||
|
||||
while ((diff % 26) >= 26) {
|
||||
while (diff % 26 >= 26) {
|
||||
diff = diff % 26
|
||||
}
|
||||
shiftedCharCode = 65 + diff
|
||||
result = shiftedCharCode
|
||||
}
|
||||
} else if ((charCode >= 97 && charCode <= 122)) {
|
||||
} else if (charCode >= 97 && charCode <= 122) {
|
||||
if (shiftedCharCode <= 96) {
|
||||
let diff = Math.abs(97 - 1 - shiftedCharCode) % 26
|
||||
|
||||
while ((diff % 26) >= 26) {
|
||||
while (diff % 26 >= 26) {
|
||||
diff = diff % 26
|
||||
}
|
||||
shiftedCharCode = 122 - diff
|
||||
@@ -129,7 +132,7 @@ function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) {
|
||||
} else if (shiftedCharCode > 122) {
|
||||
let diff = Math.abs(shiftedCharCode - 1 - 122) % 26
|
||||
|
||||
while ((diff % 26) >= 26) {
|
||||
while (diff % 26 >= 26) {
|
||||
diff = diff % 26
|
||||
}
|
||||
shiftedCharCode = 97 + diff
|
||||
|
||||
@@ -16,9 +16,36 @@
|
||||
* Non alphabetical characters (space, exclamation mark, ...) are kept as they are
|
||||
*/
|
||||
|
||||
const alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
|
||||
const alphabet = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm',
|
||||
'n',
|
||||
'o',
|
||||
'p',
|
||||
'q',
|
||||
'r',
|
||||
's',
|
||||
't',
|
||||
'u',
|
||||
'v',
|
||||
'w',
|
||||
'x',
|
||||
'y',
|
||||
'z'
|
||||
]
|
||||
|
||||
function checkKeywordValidity (keyword) {
|
||||
function checkKeywordValidity(keyword) {
|
||||
keyword.split('').forEach((char, index) => {
|
||||
const rest = keyword.slice(0, index) + keyword.slice(index + 1)
|
||||
if (rest.indexOf(char) !== -1) {
|
||||
@@ -28,7 +55,7 @@ function checkKeywordValidity (keyword) {
|
||||
return true
|
||||
}
|
||||
|
||||
function getEncryptedAlphabet (keyword) {
|
||||
function getEncryptedAlphabet(keyword) {
|
||||
const encryptedAlphabet = keyword.split('')
|
||||
alphabet.forEach((char) => {
|
||||
if (encryptedAlphabet.indexOf(char) === -1) {
|
||||
@@ -38,17 +65,20 @@ function getEncryptedAlphabet (keyword) {
|
||||
return encryptedAlphabet
|
||||
}
|
||||
|
||||
function translate (sourceAlphabet, targetAlphabet, message) {
|
||||
function translate(sourceAlphabet, targetAlphabet, message) {
|
||||
return message.split('').reduce((encryptedMessage, char) => {
|
||||
const isUpperCase = char === char.toUpperCase()
|
||||
const encryptedCharIndex = sourceAlphabet.indexOf(char.toLowerCase())
|
||||
const encryptedChar = encryptedCharIndex !== -1 ? targetAlphabet[encryptedCharIndex] : char
|
||||
encryptedMessage += isUpperCase ? encryptedChar.toUpperCase() : encryptedChar
|
||||
const encryptedChar =
|
||||
encryptedCharIndex !== -1 ? targetAlphabet[encryptedCharIndex] : char
|
||||
encryptedMessage += isUpperCase
|
||||
? encryptedChar.toUpperCase()
|
||||
: encryptedChar
|
||||
return encryptedMessage
|
||||
}, '')
|
||||
}
|
||||
|
||||
function checkInputs (keyword, message) {
|
||||
function checkInputs(keyword, message) {
|
||||
if (!keyword || !message) {
|
||||
throw new Error('Both keyword and message must be specified')
|
||||
}
|
||||
@@ -58,14 +88,22 @@ function checkInputs (keyword, message) {
|
||||
}
|
||||
}
|
||||
|
||||
function encrypt (keyword, message) {
|
||||
function encrypt(keyword, message) {
|
||||
checkInputs(keyword, message)
|
||||
return translate(alphabet, getEncryptedAlphabet(keyword.toLowerCase()), message)
|
||||
return translate(
|
||||
alphabet,
|
||||
getEncryptedAlphabet(keyword.toLowerCase()),
|
||||
message
|
||||
)
|
||||
}
|
||||
|
||||
function decrypt (keyword, message) {
|
||||
function decrypt(keyword, message) {
|
||||
checkInputs(keyword, message)
|
||||
return translate(getEncryptedAlphabet(keyword.toLowerCase()), alphabet, message)
|
||||
return translate(
|
||||
getEncryptedAlphabet(keyword.toLowerCase()),
|
||||
alphabet,
|
||||
message
|
||||
)
|
||||
}
|
||||
|
||||
export { encrypt, decrypt }
|
||||
|
||||
@@ -50,7 +50,7 @@ const morse = (msg, dot = '*', dash = '-') => {
|
||||
',': '--**--',
|
||||
'?': '**--**',
|
||||
'!': '-*-*--',
|
||||
'\'': '*----*',
|
||||
"'": '*----*',
|
||||
'"': '*-**-*',
|
||||
'(': '-*--*',
|
||||
')': '-*--*-',
|
||||
@@ -68,16 +68,21 @@ const morse = (msg, dot = '*', dash = '-') => {
|
||||
|
||||
let newMsg = ''
|
||||
|
||||
msg.toString().split('').forEach((e) => {
|
||||
if (/[a-zA-Z]/.test(e)) {
|
||||
newMsg += key[e.toUpperCase()].replaceAll('*', dot).replaceAll('-', dash)
|
||||
} else if (Object.keys(key).includes(e)) {
|
||||
newMsg += key[e].replaceAll('*', dot).replaceAll('-', dash)
|
||||
} else {
|
||||
newMsg += e
|
||||
}
|
||||
newMsg += ' '
|
||||
})
|
||||
msg
|
||||
.toString()
|
||||
.split('')
|
||||
.forEach((e) => {
|
||||
if (/[a-zA-Z]/.test(e)) {
|
||||
newMsg += key[e.toUpperCase()]
|
||||
.replaceAll('*', dot)
|
||||
.replaceAll('-', dash)
|
||||
} else if (Object.keys(key).includes(e)) {
|
||||
newMsg += key[e].replaceAll('*', dot).replaceAll('-', dash)
|
||||
} else {
|
||||
newMsg += e
|
||||
}
|
||||
newMsg += ' '
|
||||
})
|
||||
|
||||
return newMsg.trim()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @param {String} str - string to be decrypted
|
||||
* @return {String} decrypted string
|
||||
*/
|
||||
function ROT13 (str) {
|
||||
function ROT13(str) {
|
||||
if (typeof str !== 'string') {
|
||||
throw new TypeError('Argument should be string')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @param {String} str - character to check
|
||||
* @return {object} An array with the character or null if isn't a letter
|
||||
*/
|
||||
function isLetter (str) {
|
||||
function isLetter(str) {
|
||||
return str.length === 1 && str.match(/[a-zA-Z]/i)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ function isLetter (str) {
|
||||
* @param {String} character - character to check
|
||||
* @return {Boolean} result of the checking
|
||||
*/
|
||||
function isUpperCase (character) {
|
||||
function isUpperCase(character) {
|
||||
if (character === character.toUpperCase()) {
|
||||
return true
|
||||
}
|
||||
@@ -27,16 +27,22 @@ function isUpperCase (character) {
|
||||
* @param {String} key - key for encrypt
|
||||
* @return {String} result - encrypted string
|
||||
*/
|
||||
function encrypt (message, key) {
|
||||
function encrypt(message, key) {
|
||||
let result = ''
|
||||
|
||||
for (let i = 0, j = 0; i < message.length; i++) {
|
||||
const c = message.charAt(i)
|
||||
if (isLetter(c)) {
|
||||
if (isUpperCase(c)) {
|
||||
result += String.fromCharCode((c.charCodeAt(0) + key.toUpperCase().charCodeAt(j) - 2 * 65) % 26 + 65) // A: 65
|
||||
result += String.fromCharCode(
|
||||
((c.charCodeAt(0) + key.toUpperCase().charCodeAt(j) - 2 * 65) % 26) +
|
||||
65
|
||||
) // A: 65
|
||||
} else {
|
||||
result += String.fromCharCode((c.charCodeAt(0) + key.toLowerCase().charCodeAt(j) - 2 * 97) % 26 + 97) // a: 97
|
||||
result += String.fromCharCode(
|
||||
((c.charCodeAt(0) + key.toLowerCase().charCodeAt(j) - 2 * 97) % 26) +
|
||||
97
|
||||
) // a: 97
|
||||
}
|
||||
} else {
|
||||
result += c
|
||||
@@ -52,16 +58,21 @@ function encrypt (message, key) {
|
||||
* @param {String} key - key for decrypt
|
||||
* @return {String} result - decrypted string
|
||||
*/
|
||||
function decrypt (message, key) {
|
||||
function decrypt(message, key) {
|
||||
let result = ''
|
||||
|
||||
for (let i = 0, j = 0; i < message.length; i++) {
|
||||
const c = message.charAt(i)
|
||||
if (isLetter(c)) {
|
||||
if (isUpperCase(c)) {
|
||||
result += String.fromCharCode(90 - (25 - (c.charCodeAt(0) - key.toUpperCase().charCodeAt(j))) % 26)
|
||||
result += String.fromCharCode(
|
||||
90 - ((25 - (c.charCodeAt(0) - key.toUpperCase().charCodeAt(j))) % 26)
|
||||
)
|
||||
} else {
|
||||
result += String.fromCharCode(122 - (25 - (c.charCodeAt(0) - key.toLowerCase().charCodeAt(j))) % 26)
|
||||
result += String.fromCharCode(
|
||||
122 -
|
||||
((25 - (c.charCodeAt(0) - key.toLowerCase().charCodeAt(j))) % 26)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
result += c
|
||||
|
||||
@@ -14,8 +14,8 @@ const XORCipher = (str, key) => {
|
||||
throw new TypeError('Arguments type are invalid')
|
||||
}
|
||||
|
||||
return str.replace(
|
||||
/./g, (char) => String.fromCharCode(char.charCodeAt() ^ key)
|
||||
return str.replace(/./g, (char) =>
|
||||
String.fromCharCode(char.charCodeAt() ^ key)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,14 @@ describe('Testing the caesarsCipher function', () => {
|
||||
|
||||
it('Test - 2, Testing for valid string and rotation', () => {
|
||||
expect(caesarCipher('middle-Outz', 2)).toBe('okffng-Qwvb')
|
||||
expect(caesarCipher('abcdefghijklmnopqrstuvwxyz', 3)).toBe('defghijklmnopqrstuvwxyzabc')
|
||||
expect(caesarCipher('Always-Look-on-the-Bright-Side-of-Life', 5)).toBe('Fqbfdx-Qttp-ts-ymj-Gwnlmy-Xnij-tk-Qnkj')
|
||||
expect(caesarCipher('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG', 23)).toBe('QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD')
|
||||
expect(caesarCipher('abcdefghijklmnopqrstuvwxyz', 3)).toBe(
|
||||
'defghijklmnopqrstuvwxyzabc'
|
||||
)
|
||||
expect(caesarCipher('Always-Look-on-the-Bright-Side-of-Life', 5)).toBe(
|
||||
'Fqbfdx-Qttp-ts-ymj-Gwnlmy-Xnij-tk-Qnkj'
|
||||
)
|
||||
expect(
|
||||
caesarCipher('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG', 23)
|
||||
).toBe('QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { encrypt, decrypt } from '../KeywordShiftedAlphabet'
|
||||
|
||||
test('Hello world! === decrypt(encrypt(Hello world!))', () => {
|
||||
const word = 'Hello world!'
|
||||
const result = decrypt('keyword', encrypt('keyword', word))
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
test('The Algorithms === decrypt(encrypt(The Algorithms))', () => {
|
||||
const word = 'The Algorithms'
|
||||
const result = decrypt('keyword', encrypt('keyword', word))
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
import { encrypt, decrypt } from '../KeywordShiftedAlphabet'
|
||||
|
||||
test('Hello world! === decrypt(encrypt(Hello world!))', () => {
|
||||
const word = 'Hello world!'
|
||||
const result = decrypt('keyword', encrypt('keyword', word))
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
test('The Algorithms === decrypt(encrypt(The Algorithms))', () => {
|
||||
const word = 'The Algorithms'
|
||||
const result = decrypt('keyword', encrypt('keyword', word))
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
@@ -2,12 +2,16 @@ import { morse } from '../MorseCode'
|
||||
|
||||
describe('Testing morse function', () => {
|
||||
it('should return an enciphered string with a given input string', () => {
|
||||
expect(morse('Hello World!')).toBe('**** * *-** *-** --- *-- --- *-* *-** -** -*-*--')
|
||||
expect(morse('Hello World!')).toBe(
|
||||
'**** * *-** *-** --- *-- --- *-* *-** -** -*-*--'
|
||||
)
|
||||
expect(morse('1+1=2')).toBe('*---- *-*-* *---- -***- **---')
|
||||
})
|
||||
|
||||
it('should leave symbols that does not have its corresponding morse representation', () => {
|
||||
expect(morse('© 2023 GitHub, Inc.')).toBe('© **--- ----- **--- ***-- --* ** - **** **- -*** --**-- ** -* -*-* *-*-*-')
|
||||
expect(morse('© 2023 GitHub, Inc.')).toBe(
|
||||
'© **--- ----- **--- ***-- --* ** - **** **- -*** --**-- ** -* -*-* *-*-*-'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be able to accept custom morse code symbols', () => {
|
||||
|
||||
@@ -12,7 +12,11 @@ describe('Testing ROT13 function', () => {
|
||||
|
||||
it('Test - 2, passing a string as an argument', () => {
|
||||
expect(ROT13('Uryyb Jbeyq')).toBe('Hello World')
|
||||
expect(ROT13('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')).toBe('NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm')
|
||||
expect(ROT13('The quick brown fox jumps over the lazy dog')).toBe('Gur dhvpx oebja sbk whzcf bire gur ynml qbt')
|
||||
expect(ROT13('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')).toBe(
|
||||
'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
|
||||
)
|
||||
expect(ROT13('The quick brown fox jumps over the lazy dog')).toBe(
|
||||
'Gur dhvpx oebja sbk whzcf bire gur ynml qbt'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { encrypt, decrypt } from '../VigenereCipher'
|
||||
|
||||
test('Hello world! === decrypt(encrypt(Hello world!))', () => {
|
||||
const word = 'Hello world!'
|
||||
const result = decrypt(encrypt(word, 'code'), 'code')
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
test('The Algorithms === decrypt(encrypt(The Algorithms))', () => {
|
||||
const word = 'The Algorithms'
|
||||
const result = decrypt(encrypt(word, 'code'), 'code')
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
import { encrypt, decrypt } from '../VigenereCipher'
|
||||
|
||||
test('Hello world! === decrypt(encrypt(Hello world!))', () => {
|
||||
const word = 'Hello world!'
|
||||
const result = decrypt(encrypt(word, 'code'), 'code')
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
test('The Algorithms === decrypt(encrypt(The Algorithms))', () => {
|
||||
const word = 'The Algorithms'
|
||||
const result = decrypt(encrypt(word, 'code'), 'code')
|
||||
expect(result).toMatch(word)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user