mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Stopped throwing literals :/
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"test": "mocha --timeout 30000 -r ts-node/register src/*/Tests.ts",
|
"test": "mocha --timeout 60000 -r ts-node/register src/*/Tests.ts",
|
||||||
"lint": "eslint '*/*.ts' --quiet --fix",
|
"lint": "eslint '*/*.ts' --quiet --fix",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"example": "npx ts-node Example/example.ts",
|
"example": "npx ts-node Example/example.ts",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default class Decoder {
|
|||||||
|
|
||||||
checkEOS(length: number) {
|
checkEOS(length: number) {
|
||||||
if (this.index + length > this.buffer.length) {
|
if (this.index + length > this.buffer.length) {
|
||||||
throw 'end of stream'
|
throw new Error('end of stream')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next() {
|
next() {
|
||||||
@@ -48,7 +48,7 @@ export default class Decoder {
|
|||||||
if (value >= 0 && value < 16) {
|
if (value >= 0 && value < 16) {
|
||||||
return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10
|
return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10
|
||||||
}
|
}
|
||||||
throw 'invalid hex: ' + value
|
throw new Error('invalid hex: ' + value)
|
||||||
}
|
}
|
||||||
unpackNibble(value: number) {
|
unpackNibble(value: number) {
|
||||||
if (value >= 0 && value <= 9) {
|
if (value >= 0 && value <= 9) {
|
||||||
@@ -62,7 +62,7 @@ export default class Decoder {
|
|||||||
case 15:
|
case 15:
|
||||||
return '\0'.charCodeAt(0)
|
return '\0'.charCodeAt(0)
|
||||||
default:
|
default:
|
||||||
throw 'invalid nibble: ' + value
|
throw new Error('invalid nibble: ' + value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unpackByte(tag: number, value: number) {
|
unpackByte(tag: number, value: number) {
|
||||||
@@ -71,7 +71,7 @@ export default class Decoder {
|
|||||||
} else if (tag === WA.Tags.HEX_8) {
|
} else if (tag === WA.Tags.HEX_8) {
|
||||||
return this.unpackHex(value)
|
return this.unpackHex(value)
|
||||||
} else {
|
} else {
|
||||||
throw 'unknown tag: ' + tag
|
throw new Error('unknown tag: ' + tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readPacked8(tag: number) {
|
readPacked8(tag: number) {
|
||||||
@@ -90,7 +90,7 @@ export default class Decoder {
|
|||||||
}
|
}
|
||||||
readRangedVarInt(min, max, description = 'unknown') {
|
readRangedVarInt(min, max, description = 'unknown') {
|
||||||
// value =
|
// value =
|
||||||
throw 'WTF; should not be called'
|
throw new Error('WTF; should not be called')
|
||||||
}
|
}
|
||||||
isListTag(tag: number) {
|
isListTag(tag: number) {
|
||||||
return tag === WA.Tags.LIST_EMPTY || tag === WA.Tags.LIST_8 || tag === WA.Tags.LIST_16
|
return tag === WA.Tags.LIST_EMPTY || tag === WA.Tags.LIST_8 || tag === WA.Tags.LIST_16
|
||||||
@@ -104,7 +104,7 @@ export default class Decoder {
|
|||||||
case WA.Tags.LIST_16:
|
case WA.Tags.LIST_16:
|
||||||
return this.readInt(2)
|
return this.readInt(2)
|
||||||
default:
|
default:
|
||||||
throw 'invalid tag for list size: ' + tag
|
throw new Error('invalid tag for list size: ' + tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +134,12 @@ export default class Decoder {
|
|||||||
if (i && j) {
|
if (i && j) {
|
||||||
return i + '@' + j
|
return i + '@' + j
|
||||||
}
|
}
|
||||||
throw 'invalid jid pair: ' + i + ', ' + j
|
throw new Error('invalid jid pair: ' + i + ', ' + j)
|
||||||
case WA.Tags.HEX_8:
|
case WA.Tags.HEX_8:
|
||||||
case WA.Tags.NIBBLE_8:
|
case WA.Tags.NIBBLE_8:
|
||||||
return this.readPacked8(tag)
|
return this.readPacked8(tag)
|
||||||
default:
|
default:
|
||||||
throw 'invalid string with tag: ' + tag
|
throw new Error('invalid string with tag: ' + tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readAttributes(n: number) {
|
readAttributes(n: number) {
|
||||||
@@ -161,14 +161,14 @@ export default class Decoder {
|
|||||||
}
|
}
|
||||||
getToken(index: number) {
|
getToken(index: number) {
|
||||||
if (index < 3 || index >= WA.SingleByteTokens.length) {
|
if (index < 3 || index >= WA.SingleByteTokens.length) {
|
||||||
throw 'invalid token index: ' + index
|
throw new Error('invalid token index: ' + index)
|
||||||
}
|
}
|
||||||
return WA.SingleByteTokens[index]
|
return WA.SingleByteTokens[index]
|
||||||
}
|
}
|
||||||
getTokenDouble(index1, index2): string {
|
getTokenDouble(index1, index2): string {
|
||||||
const n = 256 * index1 + index2
|
const n = 256 * index1 + index2
|
||||||
if (n < 0 || n > WA.DoubleByteTokens.length) {
|
if (n < 0 || n > WA.DoubleByteTokens.length) {
|
||||||
throw 'invalid double token index: ' + n
|
throw new Error('invalid double token index: ' + n)
|
||||||
}
|
}
|
||||||
return WA.DoubleByteTokens[n]
|
return WA.DoubleByteTokens[n]
|
||||||
}
|
}
|
||||||
@@ -176,12 +176,12 @@ export default class Decoder {
|
|||||||
const listSize = this.readListSize(this.readByte())
|
const listSize = this.readListSize(this.readByte())
|
||||||
const descrTag = this.readByte()
|
const descrTag = this.readByte()
|
||||||
if (descrTag === WA.Tags.STREAM_END) {
|
if (descrTag === WA.Tags.STREAM_END) {
|
||||||
throw 'unexpected stream end'
|
throw new Error('unexpected stream end')
|
||||||
}
|
}
|
||||||
|
|
||||||
const descr = this.readString(descrTag)
|
const descr = this.readString(descrTag)
|
||||||
if (listSize === 0 || !descr) {
|
if (listSize === 0 || !descr) {
|
||||||
throw 'invalid node'
|
throw new Error('invalid node')
|
||||||
}
|
}
|
||||||
|
|
||||||
const attrs = this.readAttributes((listSize - 1) >> 1)
|
const attrs = this.readAttributes((listSize - 1) >> 1)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class Encoder {
|
|||||||
}
|
}
|
||||||
writeByteLength(length: number) {
|
writeByteLength(length: number) {
|
||||||
if (length >= 4294967296) {
|
if (length >= 4294967296) {
|
||||||
throw 'string too large to encode: ' + length
|
throw new Error('string too large to encode: ' + length)
|
||||||
}
|
}
|
||||||
if (length >= 1 << 20) {
|
if (length >= 1 << 20) {
|
||||||
this.pushByte(WA.Tags.BINARY_32)
|
this.pushByte(WA.Tags.BINARY_32)
|
||||||
@@ -51,7 +51,7 @@ export default class Encoder {
|
|||||||
if (token < 245) {
|
if (token < 245) {
|
||||||
this.pushByte(token)
|
this.pushByte(token)
|
||||||
} else if (token <= 500) {
|
} else if (token <= 500) {
|
||||||
throw 'invalid token'
|
throw new Error('invalid token')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeString(token: string, i: boolean = null) {
|
writeString(token: string, i: boolean = null) {
|
||||||
@@ -69,7 +69,7 @@ export default class Encoder {
|
|||||||
const overflow = tokenIndex - WA.Tags.SINGLE_BYTE_MAX
|
const overflow = tokenIndex - WA.Tags.SINGLE_BYTE_MAX
|
||||||
const dictionaryIndex = overflow >> 8
|
const dictionaryIndex = overflow >> 8
|
||||||
if (dictionaryIndex < 0 || dictionaryIndex > 3) {
|
if (dictionaryIndex < 0 || dictionaryIndex > 3) {
|
||||||
throw 'double byte dict token out of range: ' + token + ', ' + tokenIndex
|
throw new Error('double byte dict token out of range: ' + token + ', ' + tokenIndex)
|
||||||
}
|
}
|
||||||
this.writeToken(WA.Tags.DICTIONARY_0 + dictionaryIndex)
|
this.writeToken(WA.Tags.DICTIONARY_0 + dictionaryIndex)
|
||||||
this.writeToken(overflow % 256)
|
this.writeToken(overflow % 256)
|
||||||
@@ -118,7 +118,7 @@ export default class Encoder {
|
|||||||
this.writeByteLength(buffer.length)
|
this.writeByteLength(buffer.length)
|
||||||
this.pushBytes(buffer)
|
this.pushBytes(buffer)
|
||||||
} else {
|
} else {
|
||||||
throw 'invalid children: ' + children + ' (' + typeof children + ')'
|
throw new Error('invalid children: ' + children + ' (' + typeof children + ')')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getValidKeys(obj: Object) {
|
getValidKeys(obj: Object) {
|
||||||
@@ -128,7 +128,7 @@ export default class Encoder {
|
|||||||
if (!node) {
|
if (!node) {
|
||||||
return
|
return
|
||||||
} else if (node.length !== 3) {
|
} else if (node.length !== 3) {
|
||||||
throw 'invalid node given: ' + node
|
throw new Error('invalid node given: ' + node)
|
||||||
}
|
}
|
||||||
const validAttributes = this.getValidKeys(node[1])
|
const validAttributes = this.getValidKeys(node[1])
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ WAClientTest('Events', (client) => {
|
|||||||
const response = await client.sendMessage(testJid, 'My Name Jeff', MessageType.text)
|
const response = await client.sendMessage(testJid, 'My Name Jeff', MessageType.text)
|
||||||
await promiseTimeout(10000, waitForUpdate())
|
await promiseTimeout(10000, waitForUpdate())
|
||||||
})
|
})
|
||||||
/* it ('should update me on presence', async () => {
|
/*it ('should update me on presence', async () => {
|
||||||
//client.logUnhandledMessages = true
|
//client.logUnhandledMessages = true
|
||||||
client.setOnPresenceUpdate (presence => {
|
client.setOnPresenceUpdate (presence => {
|
||||||
console.log (presence)
|
console.log (presence)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default class WAConnectionBase {
|
|||||||
*/
|
*/
|
||||||
loadAuthInfoFromBase64(authInfo: AuthenticationCredentialsBase64 | string) {
|
loadAuthInfoFromBase64(authInfo: AuthenticationCredentialsBase64 | string) {
|
||||||
if (!authInfo) {
|
if (!authInfo) {
|
||||||
throw 'given authInfo is null'
|
throw new Error('given authInfo is null')
|
||||||
}
|
}
|
||||||
if (typeof authInfo === 'string') {
|
if (typeof authInfo === 'string') {
|
||||||
this.log(`loading authentication credentials from ${authInfo}`)
|
this.log(`loading authentication credentials from ${authInfo}`)
|
||||||
@@ -96,7 +96,7 @@ export default class WAConnectionBase {
|
|||||||
*/
|
*/
|
||||||
loadAuthInfoFromBrowser(authInfo: AuthenticationCredentialsBrowser | string) {
|
loadAuthInfoFromBrowser(authInfo: AuthenticationCredentialsBrowser | string) {
|
||||||
if (!authInfo) {
|
if (!authInfo) {
|
||||||
throw 'given authInfo is null'
|
throw new Error('given authInfo is null')
|
||||||
}
|
}
|
||||||
if (typeof authInfo === 'string') {
|
if (typeof authInfo === 'string') {
|
||||||
this.log(`loading authentication credentials from ${authInfo}`)
|
this.log(`loading authentication credentials from ${authInfo}`)
|
||||||
@@ -130,7 +130,7 @@ export default class WAConnectionBase {
|
|||||||
return this.registerCallback([parameters, null, null], callback)
|
return this.registerCallback([parameters, null, null], callback)
|
||||||
}
|
}
|
||||||
if (!Array.isArray(parameters)) {
|
if (!Array.isArray(parameters)) {
|
||||||
throw 'parameters (' + parameters + ') must be a string or array'
|
throw new Error('parameters (' + parameters + ') must be a string or array')
|
||||||
}
|
}
|
||||||
const func = 'function:' + parameters[0]
|
const func = 'function:' + parameters[0]
|
||||||
const key = parameters[1] || ''
|
const key = parameters[1] || ''
|
||||||
@@ -152,7 +152,7 @@ export default class WAConnectionBase {
|
|||||||
return this.deregisterCallback([parameters])
|
return this.deregisterCallback([parameters])
|
||||||
}
|
}
|
||||||
if (!Array.isArray(parameters)) {
|
if (!Array.isArray(parameters)) {
|
||||||
throw 'parameters (' + parameters + ') must be a string or array'
|
throw new Error('parameters (' + parameters + ') must be a string or array')
|
||||||
}
|
}
|
||||||
const func = 'function:' + parameters[0]
|
const func = 'function:' + parameters[0]
|
||||||
const key = parameters[1] || ''
|
const key = parameters[1] || ''
|
||||||
@@ -250,7 +250,7 @@ export default class WAConnectionBase {
|
|||||||
/** Send some message to the WhatsApp servers */
|
/** Send some message to the WhatsApp servers */
|
||||||
protected send(m) {
|
protected send(m) {
|
||||||
if (!this.conn) {
|
if (!this.conn) {
|
||||||
throw 'cannot send message, disconnected from WhatsApp'
|
throw new Error('cannot send message, disconnected from WhatsApp')
|
||||||
}
|
}
|
||||||
this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages
|
this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages
|
||||||
this.conn.send(m)
|
this.conn.send(m)
|
||||||
@@ -261,7 +261,7 @@ export default class WAConnectionBase {
|
|||||||
*/
|
*/
|
||||||
async logout() {
|
async logout() {
|
||||||
if (!this.conn) {
|
if (!this.conn) {
|
||||||
throw "You're not even connected, you can't log out"
|
throw new Error("You're not even connected, you can't log out")
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
this.conn.send('goodbye,["admin","Conn","disconnect"]', null, () => {
|
this.conn.send('goodbye,["admin","Conn","disconnect"]', null, () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
async connectSlim(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
|
async connectSlim(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
|
||||||
// if we're already connected, throw an error
|
// if we're already connected, throw an error
|
||||||
if (this.conn) {
|
if (this.conn) {
|
||||||
throw [1, 'already connected or connecting']
|
throw new Error('already connected or connecting')
|
||||||
}
|
}
|
||||||
// set authentication credentials if required
|
// set authentication credentials if required
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe('Test Connect', () => {
|
|||||||
assert.ok(chats)
|
assert.ok(chats)
|
||||||
if (chats.length > 0) {
|
if (chats.length > 0) {
|
||||||
assert.ok(chats[0].jid)
|
assert.ok(chats[0].jid)
|
||||||
assert.ok(chats[0].count)
|
assert.ok(chats[0].count !== null)
|
||||||
}
|
}
|
||||||
assert.ok(contacts)
|
assert.ok(contacts)
|
||||||
if (contacts.length > 0) {
|
if (contacts.length > 0) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder
|
|||||||
const commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message
|
const commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message
|
||||||
if (commaIndex < 0) {
|
if (commaIndex < 0) {
|
||||||
// if there was no comma, then this message must be not be valid
|
// if there was no comma, then this message must be not be valid
|
||||||
throw [2, 'invalid message', message]
|
throw Error ('invalid message: ' + message)
|
||||||
}
|
}
|
||||||
let data = message.slice(commaIndex+1, message.length)
|
let data = message.slice(commaIndex+1, message.length)
|
||||||
// get the message tag.
|
// get the message tag.
|
||||||
@@ -99,7 +99,7 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder
|
|||||||
} else {
|
} else {
|
||||||
if (!macKey || !encKey) {
|
if (!macKey || !encKey) {
|
||||||
// if we recieved a message that was encrypted but we don't have the keys, then there must be an error
|
// if we recieved a message that was encrypted but we don't have the keys, then there must be an error
|
||||||
throw [3, 'recieved encrypted message when auth creds not available', data]
|
throw new Error ('recieved encrypted buffer when auth creds unavailable')
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
If the data recieved was not a JSON, then it must be an encrypted message.
|
If the data recieved was not a JSON, then it must be an encrypted message.
|
||||||
@@ -115,9 +115,8 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder
|
|||||||
const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey
|
const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey
|
||||||
|
|
||||||
if (!checksum.equals(computedChecksum)) {
|
if (!checksum.equals(computedChecksum)) {
|
||||||
throw [7, "checksums don't match"]
|
throw new Error (`Checksums don't match:\nog: ${checksum.toString('hex')}\ncomputed: ${computedChecksum.toString('hex')}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the checksum the server sent, must match the one we computed for the message to be valid
|
// the checksum the server sent, must match the one we computed for the message to be valid
|
||||||
const decrypted = aesDecrypt(data, encKey) // decrypt using AES
|
const decrypted = aesDecrypt(data, encKey) // decrypt using AES
|
||||||
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
json = decoder.read(decrypted) // decode the binary message into a JSON array
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import * as Curve from 'curve25519-js'
|
|||||||
import * as Utils from './Utils'
|
import * as Utils from './Utils'
|
||||||
import WAConnectionBase from './Base'
|
import WAConnectionBase from './Base'
|
||||||
|
|
||||||
|
const StatusError = (message: any, description: string='unknown error') => new Error (`unexpected status: ${message.status} on JSON: ${JSON.stringify(message)}`)
|
||||||
|
|
||||||
export default class WAConnectionValidator extends WAConnectionBase {
|
export default class WAConnectionValidator extends WAConnectionBase {
|
||||||
/** Authenticate the connection */
|
/** Authenticate the connection */
|
||||||
protected async authenticate() {
|
protected async authenticate() {
|
||||||
@@ -37,31 +39,30 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
return this.generateKeysForAuth(json.ref)
|
return this.generateKeysForAuth(json.ref)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw [json.status, 'unknown error', json]
|
throw StatusError (json)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
if ('status' in json) {
|
if ('status' in json) {
|
||||||
switch (json.status) {
|
switch (json.status) {
|
||||||
case 401: // if the phone was unpaired
|
case 401: // if the phone was unpaired
|
||||||
throw [json.status, 'unpaired from phone', json]
|
throw StatusError (json, 'unpaired from phone')
|
||||||
case 429: // request to login was denied, don't know why it happens
|
case 429: // request to login was denied, don't know why it happens
|
||||||
throw [json.status, 'request denied, try reconnecting', json]
|
throw StatusError (json, 'request denied, try reconnecting')
|
||||||
case 304: // request to generate a new key for a QR code was denied
|
|
||||||
throw [json.status, 'request for new key denied', json]
|
|
||||||
default:
|
default:
|
||||||
throw [json.status, 'unknown error status', json]
|
throw StatusError (json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (json[1] && json[1].challenge) {
|
if (json[1] && json[1].challenge) {
|
||||||
// if its a challenge request (we get it when logging in)
|
// if its a challenge request (we get it when logging in)
|
||||||
return this.respondToChallenge(json[1].challenge).then((json) => {
|
return this.respondToChallenge(json[1].challenge)
|
||||||
if (json.status !== 200) {
|
.then((json) => {
|
||||||
// throw an error if the challenge failed
|
if (json.status !== 200) {
|
||||||
throw [json.status, 'unknown error', json]
|
// throw an error if the challenge failed
|
||||||
}
|
throw StatusError (json)
|
||||||
return this.waitForMessage('s2', []) // otherwise wait for the validation message
|
}
|
||||||
})
|
return this.waitForMessage('s2', []) // otherwise wait for the validation message
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// otherwise just chain the promise further
|
// otherwise just chain the promise further
|
||||||
return json
|
return json
|
||||||
@@ -107,7 +108,7 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
}
|
}
|
||||||
const secret = Buffer.from(json.secret, 'base64')
|
const secret = Buffer.from(json.secret, 'base64')
|
||||||
if (secret.length !== 144) {
|
if (secret.length !== 144) {
|
||||||
throw [4, 'incorrect secret length: ' + secret.length]
|
throw new Error ('incorrect secret length received: ' + secret.length)
|
||||||
}
|
}
|
||||||
// generate shared key from our private key & the secret shared by the server
|
// generate shared key from our private key & the secret shared by the server
|
||||||
const sharedKey = Curve.sharedKey(this.curveKeys.private, secret.slice(0, 32))
|
const sharedKey = Curve.sharedKey(this.curveKeys.private, secret.slice(0, 32))
|
||||||
@@ -140,11 +141,11 @@ export default class WAConnectionValidator extends WAConnectionBase {
|
|||||||
return onValidationSuccess()
|
return onValidationSuccess()
|
||||||
} else {
|
} else {
|
||||||
// if the checksums didn't match
|
// if the checksums didn't match
|
||||||
throw [5, 'HMAC validation failed']
|
throw new Error ('HMAC validation failed')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if we didn't get the connected field (usually we get this message when one opens WhatsApp on their phone)
|
// if we didn't get the connected field (usually we get this message when one opens WhatsApp on their phone)
|
||||||
throw [6, 'json connection failed', json]
|
throw new Error (`incorrect JSON: ${JSON.stringify(json)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user