Se refactoriza toda la comunicación con la API de Telegram para solucionar un problema de latencia severa en el entorno Docker. El problema era causado por un retraso en la resolución de red. - Se mejora la función en para forzar el uso de IPv4, añadir timeouts y soportar métodos GET/POST. - Se centraliza la lógica de la API en la clase , añadiendo los métodos , y . - Se modifica para que utilice los nuevos métodos centralizados, eliminando código cURL duplicado y aplicando la solución de red. - Se mantiene la instrumentación en para futuros diagnósticos, según lo solicitado.
510 lines
20 KiB
PHP
Executable File
510 lines
20 KiB
PHP
Executable File
<?php
|
|
|
|
require_once __DIR__ . '/../config/config.php';
|
|
require_once __DIR__ . '/../src/Asignacion.php';
|
|
require_once __DIR__ . '/../src/User.php';
|
|
|
|
class TelegramBot {
|
|
private $token;
|
|
private $apiUrl;
|
|
|
|
public function __construct() {
|
|
$config = require __DIR__ . '/../config/config.php';
|
|
$this->token = $config['telegram_bot_token'];
|
|
$this->apiUrl = "https://api.telegram.org/bot{$this->token}";
|
|
}
|
|
|
|
private function request($method, $data = [], $httpMethod = 'POST') {
|
|
$url = "{$this->apiUrl}/{$method}";
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
// --- MEJORAS ---
|
|
// 1. Forzar el uso de IPv4. Un problema común en entornos Docker
|
|
// es un timeout al intentar resolver AAAA (IPv6) antes de usar A (IPv4).
|
|
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
|
|
// 2. Añadir timeouts para evitar que el script se cuelgue indefinidamente.
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 segundos para conectar
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 segundos para la transferencia total
|
|
|
|
if ($httpMethod === 'POST') {
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
} else { // GET request
|
|
$url .= empty($data) ? '' : '?' . http_build_query($data);
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
}
|
|
|
|
$response = curl_exec($ch);
|
|
|
|
// Añadir manejo de errores de cURL
|
|
if (curl_errno($ch)) {
|
|
error_log("cURL Error en {$method}: " . curl_error($ch));
|
|
curl_close($ch);
|
|
return null;
|
|
}
|
|
|
|
curl_close($ch);
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
public function sendMessage($chatId, $text, $parseMode = 'HTML', $replyMarkup = null) {
|
|
$data = [
|
|
'chat_id' => $chatId,
|
|
'text' => $text,
|
|
'parse_mode' => $parseMode
|
|
];
|
|
if ($replyMarkup) {
|
|
$data['reply_markup'] = $replyMarkup;
|
|
}
|
|
return $this->request('sendMessage', $data);
|
|
}
|
|
|
|
public function getMe() {
|
|
return $this->request('getMe', [], 'GET');
|
|
}
|
|
|
|
public function getUpdates($offset = 0) {
|
|
return $this->request('getUpdates', ['offset' => $offset, 'timeout' => 60], 'GET');
|
|
}
|
|
|
|
// --- NUEVOS MÉTODOS PARA CENTRALIZAR COMUNICACIONES ---
|
|
public function getWebhookInfo() {
|
|
return $this->request('getWebhookInfo', [], 'GET');
|
|
}
|
|
|
|
public function deleteWebhook() {
|
|
return $this->request('deleteWebhook', []);
|
|
}
|
|
|
|
public function setWebhook($webhookUrl, $allowedUpdates = ['message', 'callback_query']) {
|
|
return $this->request('setWebhook', [
|
|
'url' => $webhookUrl,
|
|
'allowed_updates' => $allowedUpdates
|
|
]);
|
|
}
|
|
|
|
|
|
public function sendKeyboard($chatId, $text) {
|
|
$keyboard = [
|
|
'inline_keyboard' => [
|
|
[
|
|
['text' => 'Ver Turnos', 'callback_data' => 'ver_turnos'],
|
|
['text' => 'Semana Actual', 'callback_data' => 'semana_actual']
|
|
],
|
|
[
|
|
['text' => '📄 Mi PDF', 'callback_data' => 'mi_pdf']
|
|
],
|
|
[
|
|
['text' => '🔍 Buscar por Nombre', 'callback_data' => 'buscar_nombre']
|
|
]
|
|
]
|
|
];
|
|
|
|
return $this->sendMessage($chatId, $text, 'HTML', json_encode($keyboard));
|
|
}
|
|
|
|
public function answerCallback($callbackId, $text, $showAlert = false) {
|
|
return $this->request('answerCallbackQuery', [
|
|
'callback_query_id' => $callbackId,
|
|
'text' => $text,
|
|
'show_alert' => $showAlert
|
|
]);
|
|
}
|
|
|
|
public function deleteMessage($chatId, $messageId) {
|
|
return $this->request('deleteMessage', [
|
|
'chat_id' => $chatId,
|
|
'message_id' => $messageId
|
|
]);
|
|
}
|
|
|
|
public function editMessage($chatId, $messageId, $text, $keyboard = null) {
|
|
$data = [
|
|
'chat_id' => $chatId,
|
|
'message_id' => $messageId,
|
|
'text' => $text,
|
|
'parse_mode' => 'HTML'
|
|
];
|
|
if ($keyboard) {
|
|
$data['reply_markup'] = $keyboard;
|
|
}
|
|
return $this->request('editMessageText', $data);
|
|
}
|
|
|
|
public function getTablaTurnos($semanas = 4) {
|
|
$asignacion = new Asignacion();
|
|
$ayudantes = $asignacion->getAyudantesPorOrden();
|
|
|
|
if (empty($ayudantes)) {
|
|
return "No hay ayudantes configurados.";
|
|
}
|
|
|
|
$hoy = new DateTime();
|
|
$diaSemana = (int)$hoy->format('w');
|
|
$domingo = clone $hoy;
|
|
$domingo->modify('-' . $diaSemana . ' days');
|
|
|
|
$tabla = "<b>TABLA DE TURNOS</b>\n\n";
|
|
$tabla .= "<code>";
|
|
$tabla .= str_pad("Semana", 10) . " | " . str_pad("Ayudante", 12) . " | Periodo\n";
|
|
$tabla .= str_repeat("-", 45) . "\n";
|
|
|
|
for ($i = 0; $i < $semanas; $i++) {
|
|
$domingoSemana = clone $domingo;
|
|
$domingoSemana->modify("+{$i} weeks");
|
|
$viernesSemana = clone $domingoSemana;
|
|
$viernesSemana->modify('+5 days');
|
|
|
|
$posicion = ($i % count($ayudantes));
|
|
$ayudante = $ayudantes[$posicion];
|
|
|
|
$semanaNum = $i + 1;
|
|
$periodo = $domingoSemana->format('d/m') . '-' . $viernesSemana->format('d/m');
|
|
|
|
$tabla .= str_pad("Sem $semanaNum", 10) . " | " .
|
|
str_pad(substr($ayudante['nombre'], 0, 10), 12) . " | $periodo\n";
|
|
}
|
|
|
|
$tabla .= "</code>";
|
|
$tabla .= "\n\nCiclo: " . implode(' -> ', array_column($ayudantes, 'nombre'));
|
|
|
|
return $tabla;
|
|
}
|
|
|
|
public function getSemanaActual() {
|
|
$asignacion = new Asignacion();
|
|
|
|
$hoy = new DateTime();
|
|
$diaSemana = (int)$hoy->format('w');
|
|
$domingo = clone $hoy;
|
|
$domingo->modify('-' . $diaSemana . ' days');
|
|
|
|
$asignacionActual = $asignacion->getAsignacionPorSemana($domingo->format('Y-m-d'));
|
|
|
|
if ($asignacionActual) {
|
|
return "<b>SEMANA ACTUAL</b>\n\n" .
|
|
"Asignado: <b>{$asignacionActual['nombre']}</b>\n" .
|
|
"Periodo: " . date('d/m/Y', strtotime($asignacionActual['semana_inicio'])) .
|
|
" - " . date('d/m/Y', strtotime($asignacionActual['semana_fin'])) . "\n" .
|
|
"Dias: Domingo a Viernes";
|
|
} else {
|
|
return "No hay asignacion para esta semana.";
|
|
}
|
|
}
|
|
|
|
public function getTurnosAyudante($nombre) {
|
|
$config = require __DIR__ . '/../config/config.php';
|
|
|
|
try {
|
|
$pdo = new PDO(
|
|
"mysql:host={$config['db']['host']};port={$config['db']['port']};dbname={$config['db']['database']};charset=utf8mb4",
|
|
$config['db']['username'],
|
|
$config['db']['password'],
|
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
|
);
|
|
} catch (Exception $e) {
|
|
return "Error de conexion.";
|
|
}
|
|
|
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE nombre LIKE ? AND rol = 'ayudante' AND activo = 1 LIMIT 1");
|
|
$stmt->execute(["%$nombre%"]);
|
|
$user = $stmt->fetch();
|
|
|
|
if (!$user) {
|
|
$ayudantes = (new Asignacion())->getAyudantesPorOrden();
|
|
$nombres = implode(', ', array_map(fn($a) => $a['nombre'], $ayudantes));
|
|
return "No encontre '$nombre'.\n\nAyudantes: $nombres";
|
|
}
|
|
|
|
$stmt = $pdo->prepare("
|
|
SELECT semana_inicio, semana_fin
|
|
FROM asignaciones_turnos
|
|
WHERE user_id = ? AND semana_inicio >= CURDATE()
|
|
ORDER BY semana_inicio
|
|
LIMIT 4
|
|
");
|
|
$stmt->execute([$user['id']]);
|
|
$turnos = $stmt->fetchAll();
|
|
|
|
if (empty($turnos)) {
|
|
return "{$user['nombre']} no tiene turnos proximos.";
|
|
}
|
|
|
|
$result = "<b>TURNOS DE {$user['nombre']}</b>\n\n";
|
|
|
|
foreach ($turnos as $turno) {
|
|
$result .= date('d/m/Y', strtotime($turno['semana_inicio'])) .
|
|
" - " . date('d/m/Y', strtotime($turno['semana_fin'])) . "\n";
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function getListaAyudantesParaBusqueda() {
|
|
$ayudantes = (new Asignacion())->getAyudantesPorOrden();
|
|
return array_map(fn($a) => $a['nombre'], $ayudantes);
|
|
}
|
|
|
|
public function sendPDF($chatId, $userId) {
|
|
require_once __DIR__ . '/../src/PDFGenerator.php';
|
|
require_once __DIR__ . '/../src/User.php';
|
|
require_once __DIR__ . '/../src/DiasHorarios.php';
|
|
require_once __DIR__ . '/../src/Asignacion.php';
|
|
require_once __DIR__ . '/../src/Database.php';
|
|
|
|
$userModel = new User();
|
|
$userData = $userModel->getById($userId);
|
|
|
|
if (!$userData) {
|
|
$this->sendMessage($chatId, "Usuario no encontrado.");
|
|
return;
|
|
}
|
|
|
|
$horariosModel = new DiasHorarios();
|
|
$asignacionModel = new Asignacion();
|
|
$db = \Database::getInstance()->getConnection();
|
|
|
|
$horarios = $horariosModel->getActivos();
|
|
$ayudantes = $userModel->getAyudantesActivos();
|
|
|
|
$semanasFuturas = [];
|
|
$hoy = new DateTime();
|
|
$diaSemana = (int)$hoy->format('w');
|
|
$domingoEstaSemana = clone $hoy;
|
|
$domingoEstaSemana->modify('-' . $diaSemana . ' days');
|
|
|
|
for ($i = 0; $i <= 4; $i++) {
|
|
$semanaDomingo = clone $domingoEstaSemana;
|
|
$semanaDomingo->modify("+{$i} weeks");
|
|
$semanaInicio = $semanaDomingo->format('Y-m-d');
|
|
$asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio);
|
|
$semanasFuturas[] = [
|
|
'inicio' => $semanaInicio,
|
|
'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))),
|
|
'asignaciones' => $asignacionesSemana,
|
|
];
|
|
}
|
|
|
|
$diasNombres = [
|
|
'domingo' => 'Domingo', 'lunes' => 'Lunes', 'martes' => 'Martes',
|
|
'miercoles' => 'Miercoles', 'jueves' => 'Jueves', 'viernes' => 'Viernes', 'sabado' => 'Sabado'
|
|
];
|
|
$diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
|
|
|
$html = PDFGenerator::getStyles();
|
|
$html .= PDFGenerator::getHeader('Horarios y Turnos - ' . $userData['nombre']);
|
|
|
|
$html .= '<h2>Mis Turnos</h2>';
|
|
$html .= '<table><thead><tr><th>Semana</th><th>Periodo</th><th>Estado</th></tr></thead><tbody>';
|
|
|
|
foreach ($semanasFuturas as $index => $semana) {
|
|
$esMiTurno = !empty($semana['asignaciones']) && in_array($userId, array_column($semana['asignaciones'], 'id'));
|
|
$html .= '<tr class="' . ($esMiTurno ? 'table-success' : '') . '">';
|
|
$html .= '<td>' . date('d/m/Y', strtotime($semana['inicio']));
|
|
if ($index === 0) {
|
|
$html .= ' <span class="badge badge-primary">Actual</span>';
|
|
}
|
|
if ($esMiTurno) {
|
|
$html .= ' <span class="badge badge-success">Tu turno</span>';
|
|
}
|
|
$html .= '</td>';
|
|
$html .= '<td>' . date('d/m/Y', strtotime($semana['inicio'])) . ' - ' . date('d/m/Y', strtotime($semana['fin'])) . '</td>';
|
|
$html .= '<td>' . ($esMiTurno ? 'Asignado' : 'Sin asignar') . '</td>';
|
|
$html .= '</tr>';
|
|
}
|
|
$html .= '</tbody></table>';
|
|
|
|
$html .= '<h2>Horarios por Semana</h2>';
|
|
$html .= '<table><thead><tr><th>Semana</th>';
|
|
foreach ($diasOrden as $dia) {
|
|
$html .= '<th class="text-center">' . $diasNombres[$dia] . '</th>';
|
|
}
|
|
$html .= '</tr></thead><tbody>';
|
|
|
|
foreach ($semanasFuturas as $index => $semana) {
|
|
$esMiTurno = !empty($semana['asignaciones']) && in_array($userId, array_column($semana['asignaciones'], 'id'));
|
|
$html .= '<tr class="' . ($esMiTurno ? 'table-success' : '') . '">';
|
|
$html .= '<td>' . date('d/m', strtotime($semana['inicio']));
|
|
if ($index === 0) {
|
|
$html .= ' <span class="badge badge-primary">Actual</span>';
|
|
}
|
|
$html .= '</td>';
|
|
|
|
foreach ($diasOrden as $dia) {
|
|
$horarioDia = null;
|
|
foreach ($horarios as $h) {
|
|
if ($h['dia_semana'] === $dia) {
|
|
$horarioDia = $h;
|
|
break;
|
|
}
|
|
}
|
|
$esActivo = $horarioDia && $horarioDia['activo'];
|
|
|
|
if ($esMiTurno && $esActivo) {
|
|
$html .= '<td class="text-center" style="background-color: #198754; color: white;">';
|
|
$html .= date('H:i', strtotime($horarioDia['hora_apertura'])) . '<br>' . date('H:i', strtotime($horarioDia['hora_cierre']));
|
|
$html .= '</td>';
|
|
} elseif (!$esActivo) {
|
|
$html .= '<td class="text-center text-muted">Cerrado</td>';
|
|
} else {
|
|
$html .= '<td class="text-center">' . date('H:i', strtotime($horarioDia['hora_apertura'])) . '<br>' . date('H:i', strtotime($horarioDia['hora_cierre'])) . '</td>';
|
|
}
|
|
}
|
|
$html .= '</tr>';
|
|
}
|
|
$html .= '</tbody></table>';
|
|
$html .= PDFGenerator::getFooter();
|
|
|
|
$pdfGenerator = new PDFGenerator();
|
|
$pdfContent = $pdfGenerator->generateFromHtml($html);
|
|
|
|
$pdfFile = tempnam(sys_get_temp_dir(), 'turnos');
|
|
file_put_contents($pdfFile, $pdfContent);
|
|
|
|
$this->sendDocument($chatId, $pdfFile, 'mis-turnos-' . date('Y-m-d') . '.pdf');
|
|
|
|
unlink($pdfFile);
|
|
}
|
|
|
|
public function sendDocument($chatId, $documentPath, $filename) {
|
|
$url = "{$this->apiUrl}/sendDocument";
|
|
$postFields = [
|
|
'chat_id' => $chatId,
|
|
'document' => new CURLFile($documentPath, 'application/pdf', $filename)
|
|
];
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
}
|
|
|
|
public function sendPDFGeneral($chatId) {
|
|
require_once __DIR__ . '/../src/PDFGenerator.php';
|
|
require_once __DIR__ . '/../src/User.php';
|
|
require_once __DIR__ . '/../src/DiasHorarios.php';
|
|
require_once __DIR__ . '/../src/Asignacion.php';
|
|
require_once __DIR__ . '/../src/Database.php';
|
|
|
|
$userModel = new User();
|
|
$horariosModel = new DiasHorarios();
|
|
$asignacionModel = new Asignacion();
|
|
$db = \Database::getInstance()->getConnection();
|
|
|
|
$horarios = $horariosModel->getActivos();
|
|
$ayudantes = $userModel->getAyudantesActivos();
|
|
|
|
$semanasFuturas = [];
|
|
$hoy = new DateTime();
|
|
$diaSemana = (int)$hoy->format('w');
|
|
$domingoEstaSemana = clone $hoy;
|
|
$domingoEstaSemana->modify('-' . $diaSemana . ' days');
|
|
|
|
for ($i = 0; $i <= 4; $i++) {
|
|
$semanaDomingo = clone $domingoEstaSemana;
|
|
$semanaDomingo->modify("+{$i} weeks");
|
|
$semanaInicio = $semanaDomingo->format('Y-m-d');
|
|
$asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio);
|
|
$semanasFuturas[] = [
|
|
'inicio' => $semanaInicio,
|
|
'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))),
|
|
'asignaciones' => $asignacionesSemana,
|
|
];
|
|
}
|
|
|
|
$diasNombres = [
|
|
'domingo' => 'Domingo', 'lunes' => 'Lunes', 'martes' => 'Martes',
|
|
'miercoles' => 'Miercoles', 'jueves' => 'Jueves', 'viernes' => 'Viernes', 'sabado' => 'Sabado'
|
|
];
|
|
$diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
|
|
|
|
$html = PDFGenerator::getStyles();
|
|
$html .= PDFGenerator::getHeader('Horarios y Turnos - Todos los Ayudantes');
|
|
|
|
$html .= '<h2>Turnos de Ayudantes</h2>';
|
|
$html .= '<table><thead><tr><th>Ayudante</th><th>Fecha 1</th><th>Fecha 2</th><th>Fecha 3</th><th>Fecha 4</th></tr></thead><tbody>';
|
|
|
|
foreach ($ayudantes as $ayudante) {
|
|
$stmt = $db->prepare("SELECT semana_inicio, semana_fin FROM asignaciones_turnos WHERE user_id = ? AND semana_inicio >= CURDATE() ORDER BY semana_inicio LIMIT 4");
|
|
$stmt->execute([$ayudante['id']]);
|
|
$turnos = $stmt->fetchAll();
|
|
|
|
$html .= '<tr>';
|
|
$html .= '<td>' . htmlspecialchars($ayudante['nombre']) . '</td>';
|
|
for ($i = 0; $i < 4; $i++) {
|
|
if (isset($turnos[$i])) {
|
|
$html .= '<td class="text-center">' . date('d/m/Y', strtotime($turnos[$i]['semana_inicio'])) . ' - ' . date('d/m/Y', strtotime($turnos[$i]['semana_fin'])) . '</td>';
|
|
} else {
|
|
$html .= '<td class="text-center text-muted">-</td>';
|
|
}
|
|
}
|
|
$html .= '</tr>';
|
|
}
|
|
$html .= '</tbody></table>';
|
|
|
|
$html .= '<h2>Horarios por Semana</h2>';
|
|
$html .= '<table><thead><tr><th>Semana</th>';
|
|
foreach ($diasOrden as $dia) {
|
|
$html .= '<th class="text-center">' . $diasNombres[$dia] . '</th>';
|
|
}
|
|
$html .= '</tr></thead><tbody>';
|
|
|
|
foreach ($semanasFuturas as $index => $semana) {
|
|
$html .= '<tr>';
|
|
$html .= '<td>' . date('d/m', strtotime($semana['inicio']));
|
|
if ($index === 0) {
|
|
$html .= ' <span class="badge badge-primary">Actual</span>';
|
|
}
|
|
$html .= '</td>';
|
|
|
|
foreach ($diasOrden as $dia) {
|
|
$horarioDia = null;
|
|
foreach ($horarios as $h) {
|
|
if ($h['dia_semana'] === $dia) {
|
|
$horarioDia = $h;
|
|
break;
|
|
}
|
|
}
|
|
$esActivo = $horarioDia && $horarioDia['activo'];
|
|
|
|
if ($esActivo) {
|
|
$html .= '<td class="text-center">' . date('H:i', strtotime($horarioDia['hora_apertura'])) . '<br>' . date('H:i', strtotime($horarioDia['hora_cierre'])) . '</td>';
|
|
} else {
|
|
$html .= '<td class="text-center text-muted">Cerrado</td>';
|
|
}
|
|
}
|
|
$html .= '</tr>';
|
|
}
|
|
$html .= '</tbody></table>';
|
|
|
|
$html .= '<h2>Horarios de Apertura</h2>';
|
|
$html .= '<table><thead><tr><th>Dia</th><th>Hora Apertura</th><th>Hora Cierre</th><th>Estado</th></tr></thead><tbody>';
|
|
foreach ($horarios as $h) {
|
|
$html .= '<tr>';
|
|
$html .= '<td>' . ucfirst($h['dia_semana']) . '</td>';
|
|
$html .= '<td>' . date('H:i', strtotime($h['hora_apertura'])) . '</td>';
|
|
$html .= '<td>' . date('H:i', strtotime($h['hora_cierre'])) . '</td>';
|
|
$html .= '<td>' . ($h['activo'] ? 'Abierto' : 'Cerrado') . '</td>';
|
|
$html .= '</tr>';
|
|
}
|
|
$html .= '</tbody></table>';
|
|
$html .= PDFGenerator::getFooter();
|
|
|
|
$pdfGenerator = new PDFGenerator();
|
|
$pdfContent = $pdfGenerator->generateFromHtml($html);
|
|
|
|
$pdfFile = tempnam(sys_get_temp_dir(), 'turnos');
|
|
file_put_contents($pdfFile, $pdfContent);
|
|
|
|
$this->sendDocument($chatId, $pdfFile, 'turnos-contenedor-' . date('Y-m-d') . '.pdf');
|
|
|
|
unlink($pdfFile);
|
|
}
|
|
}
|