feat: nova conversa com número avulso + normalização WhatsApp + dark mode no modal
- Botão '+' na sidebar para iniciar conversa com qualquer número - Busca automática: cliente → dependente → número novo - Campo de mensagem inicial personalizável no modal - Normalização de número (DDI 55) ao enviar via Evolution API - Modal de nova conversa com suporte a dark mode - Correção: foto do cliente não é mais atualizada a cada mensagem - Correção: triagem não processa respostas se já tem atendente+equipe
This commit is contained in:
@@ -794,7 +794,9 @@ class ChatController {
|
|||||||
static async createConversation(req, res) {
|
static async createConversation(req, res) {
|
||||||
try {
|
try {
|
||||||
const { alias } = req.params;
|
const { alias } = req.params;
|
||||||
const { empresaId, numero, nomeContato, mensagem, instanciaId, clienteId } = req.body;
|
const { empresaId, numero, mensagem, instanciaId } = req.body;
|
||||||
|
let clienteId = req.body.clienteId || null;
|
||||||
|
let nomeContato = req.body.nomeContato || '';
|
||||||
|
|
||||||
if (!empresaId || !numero || !mensagem) {
|
if (!empresaId || !numero || !mensagem) {
|
||||||
return res.status(400).json({ success: false, error: 'Campos obrigatórios: empresaId, numero, mensagem' });
|
return res.status(400).json({ success: false, error: 'Campos obrigatórios: empresaId, numero, mensagem' });
|
||||||
@@ -808,6 +810,60 @@ class ChatController {
|
|||||||
|
|
||||||
const numeroLimpo = numero.replace(/\D/g, '').substring(0, 15);
|
const numeroLimpo = numero.replace(/\D/g, '').substring(0, 15);
|
||||||
|
|
||||||
|
// Se não foi informado clienteId, tenta localizar pelo número de telefone
|
||||||
|
// Fluxo: cliente → dependente (titular) → número novo (sem vínculo)
|
||||||
|
let clienteEncontrado = null;
|
||||||
|
let nomeEncontrado = null;
|
||||||
|
if (!clienteId) {
|
||||||
|
// Variações do número para busca: completo, sem DDI (55), últimos 8 dígitos
|
||||||
|
const variantes = [numeroLimpo];
|
||||||
|
if (numeroLimpo.startsWith('55')) variantes.push(numeroLimpo.substring(2));
|
||||||
|
const ultimos8 = numeroLimpo.slice(-8);
|
||||||
|
if (!variantes.includes(ultimos8)) variantes.push(ultimos8);
|
||||||
|
|
||||||
|
// 1) Busca CLIENTES
|
||||||
|
for (const termo of variantes) {
|
||||||
|
const cli = await db.query(alias, `
|
||||||
|
SELECT CLI_CODIGO_ID, CLI_NOME
|
||||||
|
FROM CLIENTES
|
||||||
|
WHERE CLI_EMPRESA_ID = ?
|
||||||
|
AND (REPLACE(REPLACE(REPLACE(COALESCE(CLI_CELULAR,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
|
||||||
|
OR REPLACE(REPLACE(REPLACE(COALESCE(CLI_FONE1,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
|
||||||
|
OR REPLACE(REPLACE(REPLACE(COALESCE(CLI_FONE2,''),'-',''),'(',''),')','') LIKE '%' || ? || '%')
|
||||||
|
FETCH FIRST 1 ROWS ONLY
|
||||||
|
`, [empresaId, termo, termo, termo]);
|
||||||
|
if (cli.length > 0) {
|
||||||
|
clienteEncontrado = cli[0].CLI_CODIGO_ID;
|
||||||
|
nomeEncontrado = (cli[0].CLI_NOME || '').trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Se não achou cliente, busca DEPENDENTES
|
||||||
|
if (!clienteEncontrado) {
|
||||||
|
for (const termo of variantes) {
|
||||||
|
const dep = await db.query(alias, `
|
||||||
|
SELECT d.DEPC_CLIENTE_ID AS TITULAR_ID, d.DEPC_NOME, c.CLI_NOME AS TITULAR_NOME
|
||||||
|
FROM DEPENDENTES_CLI d
|
||||||
|
LEFT JOIN CLIENTES c ON d.DEPC_CLIENTE_ID = c.CLI_CODIGO_ID
|
||||||
|
WHERE d.DEPC_EMPRESA_ID = ? AND d.DEPC_SITUACAO = 'A'
|
||||||
|
AND REPLACE(REPLACE(REPLACE(COALESCE(d.DEPC_TELEFONE,''),'-',''),'(',''),')','') LIKE '%' || ? || '%'
|
||||||
|
FETCH FIRST 1 ROWS ONLY
|
||||||
|
`, [empresaId, termo]);
|
||||||
|
if (dep.length > 0) {
|
||||||
|
clienteEncontrado = dep[0].TITULAR_ID;
|
||||||
|
nomeEncontrado = (dep[0].TITULAR_NOME || dep[0].DEPC_NOME || '').trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clienteEncontrado) {
|
||||||
|
clienteId = clienteEncontrado;
|
||||||
|
if (!nomeContato || nomeContato === numeroLimpo) nomeContato = nomeEncontrado;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifica se já existe conversa ABERTA ou EM ESPERA para este número
|
// Verifica se já existe conversa ABERTA ou EM ESPERA para este número
|
||||||
// Tenta com o número exato, e também com os últimos 8 dígitos
|
// Tenta com o número exato, e também com os últimos 8 dígitos
|
||||||
let existente = await db.query(alias, `
|
let existente = await db.query(alias, `
|
||||||
@@ -1416,14 +1472,22 @@ class ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: normaliza número para formato WhatsApp (DDI + 9º dígito)
|
||||||
|
function _normalizarWhatsApp(numero) {
|
||||||
|
let n = String(numero || '').replace(/\D/g, '');
|
||||||
|
if (!n) return '';
|
||||||
|
if (!n.startsWith('55')) n = '55' + n;
|
||||||
|
const resto = n.slice(2);
|
||||||
|
if (resto.length === 10) n = '55' + resto.slice(0, 2) + '9' + resto.slice(2);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
// Função auxiliar para enviar mensagem via Evolution API
|
// Função auxiliar para enviar mensagem via Evolution API
|
||||||
async function sendEvolutionMessage(instancia, numero, texto, tipo, midiaBase64, nomeArquivo) {
|
async function sendEvolutionMessage(instancia, numero, texto, tipo, midiaBase64, nomeArquivo) {
|
||||||
const url = ((instancia.INS_URL || '').trim()).replace(/\/$/, '');
|
const url = ((instancia.INS_URL || '').trim()).replace(/\/$/, '');
|
||||||
const apiKey = (instancia.INS_API_KEY || '').trim();
|
const apiKey = (instancia.INS_API_KEY || '').trim();
|
||||||
const instanceName = (instancia.INS_INSTANCE_NAME || '').trim();
|
const instanceName = (instancia.INS_INSTANCE_NAME || '').trim();
|
||||||
const numeroLimpo = numero.replace(/\D/g, '');
|
const numeroLimpo = _normalizarWhatsApp(numero);
|
||||||
|
|
||||||
if (!url || !apiKey || !instanceName || !numeroLimpo) return { success: false, error: 'Parametros insuficientes' };
|
|
||||||
|
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ function logTriage(msg) {
|
|||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: normaliza número para formato WhatsApp (DDI + 9º dígito)
|
||||||
|
function _normalizarWhatsApp(numero) {
|
||||||
|
let n = String(numero || '').replace(/\D/g, '');
|
||||||
|
if (!n) return '';
|
||||||
|
if (!n.startsWith('55')) n = '55' + n;
|
||||||
|
const resto = n.slice(2);
|
||||||
|
if (resto.length === 10) n = '55' + resto.slice(0, 2) + '9' + resto.slice(2);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
class TriageController {
|
class TriageController {
|
||||||
/**
|
/**
|
||||||
* Envia o menu principal (equipes + boleto)
|
* Envia o menu principal (equipes + boleto)
|
||||||
@@ -784,7 +794,7 @@ class TriageController {
|
|||||||
// Envia PDF via Evolution
|
// Envia PDF via Evolution
|
||||||
var fileName = matricula + ' - ' + fmtDt(t.CAR_DT_VENCIMENTO) + '.pdf';
|
var fileName = matricula + ' - ' + fmtDt(t.CAR_DT_VENCIMENTO) + '.pdf';
|
||||||
var mediaPayload = JSON.stringify({
|
var mediaPayload = JSON.stringify({
|
||||||
number: numero.replace(/\D/g, ''),
|
number: _normalizarWhatsApp(numero),
|
||||||
mediatype: 'document',
|
mediatype: 'document',
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
caption: 'Boleto Sistema',
|
caption: 'Boleto Sistema',
|
||||||
@@ -1031,7 +1041,8 @@ class TriageController {
|
|||||||
const instanceName = (inst[0].INS_INSTANCE_NAME || '').trim();
|
const instanceName = (inst[0].INS_INSTANCE_NAME || '').trim();
|
||||||
if (!url || !apiKey || !instanceName) return;
|
if (!url || !apiKey || !instanceName) return;
|
||||||
|
|
||||||
const numeroLimpo = numero.replace(/\D/g, '');
|
// Normaliza para formato WhatsApp (com DDI)
|
||||||
|
const numeroLimpo = _normalizarWhatsApp(numero);
|
||||||
const payload = JSON.stringify({ number: numeroLimpo, text: texto || '', delay: 0 });
|
const payload = JSON.stringify({ number: numeroLimpo, text: texto || '', delay: 0 });
|
||||||
const endpoint = '/message/sendText/' + encodeURIComponent(instanceName);
|
const endpoint = '/message/sendText/' + encodeURIComponent(instanceName);
|
||||||
const parsedUrl = new URL(url + endpoint);
|
const parsedUrl = new URL(url + endpoint);
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ body.dark-mode .msg.enviando { opacity: 0.4; }
|
|||||||
</div>
|
</div>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input type="text" id="searchConv" placeholder="🔍 Buscar conversas..." onkeyup="carregarConversas()">
|
<input type="text" id="searchConv" placeholder="🔍 Buscar conversas..." onkeyup="carregarConversas()">
|
||||||
|
<button id="btnNovaConversa" onclick="mostrarModalNovaConversa()" title="Nova conversa" style="background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.2);color:#fff;border-radius:8px;width:32px;height:32px;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s" onmouseover="this.style.background='rgba(255,255,255,0.22)'" onmouseout="this.style.background='rgba(255,255,255,0.12)'">+</button>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="chkFinalizadas" onchange="carregarConversas()"> Finalizadas
|
<input type="checkbox" id="chkFinalizadas" onchange="carregarConversas()"> Finalizadas
|
||||||
</label>
|
</label>
|
||||||
@@ -2132,6 +2133,61 @@ carregarConfigResolucao().then(function() {
|
|||||||
if (conversaId) abrirConversa(parseInt(conversaId));
|
if (conversaId) abrirConversa(parseInt(conversaId));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ===== NOVA CONVERSA (número não cadastrado) =====
|
||||||
|
window.mostrarModalNovaConversa = function() {
|
||||||
|
document.getElementById('modalNovaConversa').style.display = 'flex';
|
||||||
|
document.getElementById('novoNumero').value = '';
|
||||||
|
document.getElementById('novoNome').value = '';
|
||||||
|
document.getElementById('novoMensagem').value = '';
|
||||||
|
document.getElementById('novoNumero').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.fecharModalNovaConversa = function() {
|
||||||
|
document.getElementById('modalNovaConversa').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.criarNovaConversa = async function() {
|
||||||
|
const numero = document.getElementById('novoNumero').value.replace(/\D/g, '');
|
||||||
|
const nome = document.getElementById('novoNome').value.trim() || numero;
|
||||||
|
const mensagem = document.getElementById('novoMensagem').value.trim() || '👋 Olá! Em que posso ajudar?';
|
||||||
|
|
||||||
|
if (!numero || numero.length < 10) {
|
||||||
|
if (Chatc2Toast) Chatc2Toast.warn('Informe um número de WhatsApp válido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnCriarConversa');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Criando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/' + alias + '/conversations/create', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||||
|
body: JSON.stringify({
|
||||||
|
empresaId: empresaId,
|
||||||
|
numero: numero,
|
||||||
|
nomeContato: nome,
|
||||||
|
mensagem: mensagem,
|
||||||
|
instanciaId: null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
fecharModalNovaConversa();
|
||||||
|
await carregarConversas();
|
||||||
|
abrirConversa(data.data.id);
|
||||||
|
if (Chatc2Toast) Chatc2Toast.success('Conversa iniciada com ' + nome);
|
||||||
|
} else {
|
||||||
|
if (Chatc2Toast) Chatc2Toast.error(data.error || 'Erro ao criar conversa');
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
if (Chatc2Toast) Chatc2Toast.error('Erro de conexão ao criar conversa');
|
||||||
|
}
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Iniciar conversa';
|
||||||
|
}
|
||||||
|
|
||||||
// ===== LOGOUT =====
|
// ===== LOGOUT =====
|
||||||
window.logout = function() {
|
window.logout = function() {
|
||||||
['chatc2_token','chatc2_alias','chatc2_user'].forEach(function(k) { localStorage.removeItem(k); });
|
['chatc2_token','chatc2_alias','chatc2_user'].forEach(function(k) { localStorage.removeItem(k); });
|
||||||
@@ -2170,6 +2226,71 @@ document.getElementById('msgInput').addEventListener('input', function() {
|
|||||||
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
/* Dark mode para o modal Nova Conversa */
|
||||||
|
body.dark-mode #modalNovaConversa > div {
|
||||||
|
background: #1e1e2e !important;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.5) !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa h3 {
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa p {
|
||||||
|
color: #9ca3af !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa label {
|
||||||
|
color: #d1d5db !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa input,
|
||||||
|
body.dark-mode #modalNovaConversa textarea {
|
||||||
|
background: #2a2a3e !important;
|
||||||
|
border-color: #3f3f5a !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa input:focus,
|
||||||
|
body.dark-mode #modalNovaConversa textarea:focus {
|
||||||
|
border-color: #818cf8 !important;
|
||||||
|
background: #2a2a3e !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(129,140,248,0.15) !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa input::placeholder,
|
||||||
|
body.dark-mode #modalNovaConversa textarea::placeholder {
|
||||||
|
color: #6b7280 !important;
|
||||||
|
}
|
||||||
|
body.dark-mode #modalNovaConversa button {
|
||||||
|
color: #d1d5db !important;
|
||||||
|
}
|
||||||
|
/* Botão Cancelar no dark mode */
|
||||||
|
body.dark-mode #btnCancelarConversa {
|
||||||
|
background: #2a2a3e !important;
|
||||||
|
border-color: #3f3f5a !important;
|
||||||
|
color: #d1d5db !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Modal: Nova Conversa -->
|
||||||
|
<div id="modalNovaConversa" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10000;align-items:center;justify-content:center" onclick="if(event.target===this) fecharModalNovaConversa()">
|
||||||
|
<div style="background:#fff;border-radius:16px;padding:32px 28px 24px;width:100%;max-width:420px;box-shadow:0 20px 60px rgba(0,0,0,0.18)" onclick="event.stopPropagation()">
|
||||||
|
<h3 style="margin:0 0 6px;font-size:18px;font-weight:700;color:#111827">Nova conversa</h3>
|
||||||
|
<p style="margin:0 0 18px;font-size:13px;color:#6b7280">Inicie um atendimento para um número que ainda não está no sistema.</p>
|
||||||
|
<div class="form-group" style="margin-bottom:14px">
|
||||||
|
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Número (WhatsApp) *</label>
|
||||||
|
<input type="tel" id="novoNumero" placeholder="(11) 99999-9999" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:14px">
|
||||||
|
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Nome do contato</label>
|
||||||
|
<input type="text" id="novoNome" placeholder="Nome (opcional)" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:14px">
|
||||||
|
<label style="display:block;font-size:13px;font-weight:600;color:#374151;margin-bottom:5px">Mensagem inicial</label>
|
||||||
|
<textarea id="novoMensagem" rows="3" placeholder="👋 Olá! Em que posso ajudar?" style="width:100%;padding:10px 14px;border:2px solid #e5e7eb;border-radius:8px;font-size:14px;outline:none;background:#f9fafb;resize:vertical;font-family:inherit;transition:all .15s" onfocus="this.style.borderColor='#667eea';this.style.background='#fff'" onblur="this.style.borderColor='#e5e7eb';this.style.background='#f9fafb'"></textarea>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px">
|
||||||
|
<button id="btnCancelarConversa" onclick="fecharModalNovaConversa()" style="padding:10px 20px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;color:#374151;font-size:14px;font-weight:600;cursor:pointer">Cancelar</button>
|
||||||
|
<button id="btnCriarConversa" onclick="criarNovaConversa()" style="padding:10px 20px;border:none;border-radius:8px;background:#667eea;color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s" onmouseover="this.style.background='#5a67d8'" onmouseout="this.style.background='#667eea'">Iniciar conversa</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/dark-mode.js"></script>
|
<script src="/js/dark-mode.js"></script>
|
||||||
<script src="/js/notifications.js"></script>
|
<script src="/js/notifications.js"></script>
|
||||||
<script src="/js/toast.js"></script>
|
<script src="/js/toast.js"></script>
|
||||||
|
|||||||
@@ -68,8 +68,26 @@ function _request(baseUrl, apiKey, endpoint, method, body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Função auxiliar: extrai credenciais da instância
|
// Função auxiliar: normaliza número para formato WhatsApp
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
function _normalizarNumero(numero) {
|
||||||
|
let n = String(numero || '').replace(/\D/g, '');
|
||||||
|
if (!n) return '';
|
||||||
|
// Adiciona DDI Brasil (55) se não tiver código de país
|
||||||
|
if (!n.startsWith('55') && !n.startsWith('1') && !n.startsWith('3') &&
|
||||||
|
!n.startsWith('4') && !n.startsWith('6') && !n.startsWith('7') &&
|
||||||
|
!n.startsWith('8') && !n.startsWith('9')) {
|
||||||
|
n = '55' + n;
|
||||||
|
}
|
||||||
|
// Corrige celular sem o 9º dígito: DDD(2) + 8 dígitos → DDD(2) + 9 + 8 dígitos
|
||||||
|
if (n.startsWith('55')) {
|
||||||
|
const resto = n.slice(2);
|
||||||
|
if (resto.length === 10) {
|
||||||
|
n = '55' + resto.slice(0, 2) + '9' + resto.slice(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
function _extrairCredenciais(instancia) {
|
function _extrairCredenciais(instancia) {
|
||||||
return {
|
return {
|
||||||
url: ((instancia.INS_URL || '').trim()).replace(/\/+$/, ''),
|
url: ((instancia.INS_URL || '').trim()).replace(/\/+$/, ''),
|
||||||
@@ -134,7 +152,7 @@ async function sendMedia(instancia, numero, tipo, midiaBase64, nomeArquivo) {
|
|||||||
const { url, apiKey, instanceName } = _extrairCredenciais(instancia);
|
const { url, apiKey, instanceName } = _extrairCredenciais(instancia);
|
||||||
if (!url || !apiKey || !instanceName || !numero || !midiaBase64) return;
|
if (!url || !apiKey || !instanceName || !numero || !midiaBase64) return;
|
||||||
|
|
||||||
const numeroLimpo = numero.replace(/\D/g, '');
|
const numeroLimpo = _normalizarNumero(numero);
|
||||||
const fileName = nomeArquivo || 'arquivo';
|
const fileName = nomeArquivo || 'arquivo';
|
||||||
|
|
||||||
let endpoint, payload;
|
let endpoint, payload;
|
||||||
|
|||||||
Reference in New Issue
Block a user