Files
flash-call-otp/backend/includes/WhatsApp.php
2026-05-23 18:34:21 +03:00

267 lines
9.6 KiB
PHP

<?php
/**
* WhatsApp Gateway Client Helper
*/
class WhatsAppClient {
private static $gatewayUrl;
private static $secret;
private static $sessionKey;
private static function init() {
if (!self::$gatewayUrl) {
self::$gatewayUrl = rtrim(defined('WHATSAPP_GATEWAY_URL') ? WHATSAPP_GATEWAY_URL : 'http://localhost:3732', '/');
self::$secret = defined('WHATSAPP_WEBHOOK_SECRET') ? WHATSAPP_WEBHOOK_SECRET : '';
self::$sessionKey = defined('WHATSAPP_SESSION_KEY') ? WHATSAPP_SESSION_KEY : 'otp_session';
}
}
/**
* Check if a phone number is registered on WhatsApp.
*
* @param string $phone Phone number in E.164 format (e.g. +96279XXXXXXXX)
* @return bool True if registered, false otherwise
*/
public static function isAvailable($phone) {
self::init();
$cleanPhone = preg_replace('/[^\d]/', '', $phone); // Strip '+' and other non-digits
$payload = json_encode([
'session_key' => self::$sessionKey,
'phone' => $cleanPhone
]);
$ch = curl_init(self::$gatewayUrl . '/api/contacts/check');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Webhook-Secret: ' . self::$secret
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && $response) {
$data = json_decode($response, true);
if (isset($data['status']) && $data['status'] === 'success') {
return isset($data['data']['exists']) && $data['data']['exists'] === true;
}
}
return false;
}
/**
* Send OTP message via WhatsApp Gateway.
*
* @param string $phone Destination phone number
* @param string $message Message text
* @param string|null $imageBase64 Optional base64 image data
* @return bool True if successfully sent
*/
public static function sendMessage($phone, $message, $imageBase64 = null) {
self::init();
$cleanPhone = preg_replace('/[^\d]/', '', $phone);
$payloadData = [
'session_key' => self::$sessionKey,
'phone' => $cleanPhone,
'message' => $message
];
if ($imageBase64) {
$payloadData['image'] = $imageBase64;
}
$payload = json_encode($payloadData);
$ch = curl_init(self::$gatewayUrl . '/api/messages/send');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Webhook-Secret: ' . self::$secret
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && $response) {
$data = json_decode($response, true);
return isset($data['status']) && $data['status'] === 'success';
}
return false;
}
/**
* Ensure the TTF fonts exist in the backend/fonts directory.
* If they don't, try to download them.
*/
private static function ensureFontsExist() {
$fontDir = __DIR__ . '/../fonts';
if (!is_dir($fontDir)) {
@mkdir($fontDir, 0755, true);
}
$fonts = [
'Roboto-Bold.ttf' => 'https://github.com/google/fonts/raw/main/apache/roboto/static/Roboto-Bold.ttf',
'Lora-Bold.ttf' => 'https://github.com/google/fonts/raw/main/ofl/lora/Lora-Bold.ttf'
];
foreach ($fonts as $filename => $url) {
$path = $fontDir . '/' . $filename;
if (!file_exists($path) || @filesize($path) < 20000) {
// Try file_get_contents first
$content = @file_get_contents($url);
if ($content !== false && strlen($content) > 20000) {
@file_put_contents($path, $content);
} elseif (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$content = curl_exec($ch);
curl_close($ch);
if ($content && strlen($content) > 20000) {
@file_put_contents($path, $content);
}
}
}
}
}
/**
* Generate dynamic base64-encoded image containing OTP code using GD library.
*
* @param string $otp 3-digit OTP code
* @return string Base64 encoded PNG image
*/
public static function generateOtpImageBase64($otp) {
// Ensure fonts exist (downloads them if missing)
self::ensureFontsExist();
// Create a 300x100 image
$im = imagecreatetruecolor(300, 100);
if (!$im) {
return '';
}
// Colors
$bgColor = imagecolorallocate($im, 240, 244, 248); // Soft grey-blue
$textColor = imagecolorallocate($im, 33, 37, 41); // Dark charcoal
$noiseColor = imagecolorallocate($im, 200, 210, 220); // Light noise
$otpFg = imagecolorallocate($im, 13, 110, 253); // Royal blue for OTP
// Fill background
imagefill($im, 0, 0, $bgColor);
// --- 1. Check if TTF is available and fonts are loaded ---
$useTtf = false;
$availableFonts = [];
if (function_exists('imagettftext')) {
$fontDir = __DIR__ . '/../fonts';
if (file_exists($fontDir . '/Roboto-Bold.ttf') && @filesize($fontDir . '/Roboto-Bold.ttf') > 20000) {
$availableFonts[] = $fontDir . '/Roboto-Bold.ttf';
}
if (file_exists($fontDir . '/Lora-Bold.ttf') && @filesize($fontDir . '/Lora-Bold.ttf') > 20000) {
$availableFonts[] = $fontDir . '/Lora-Bold.ttf';
}
if (!empty($availableFonts)) {
$useTtf = true;
}
}
$chars = str_split($otp);
if ($useTtf) {
// Render smooth OTP using TTF fonts
// Center the text block roughly. A 3-digit OTP with size 30px needs ~120px width.
$startX = random_int(85, 115);
$yBase = 65; // Baseline of TTF text
foreach ($chars as $char) {
// Alternating / random font selection per digit
$fontFile = $availableFonts[array_rand($availableFonts)];
$angle = random_int(-15, 15); // Dynamic tilts to thwart OCR
$size = random_int(28, 34); // Dynamic size
$yOffset = random_int(-6, 6); // Vertical jitter
// Draw character
imagettftext($im, $size, $angle, $startX, $yBase + $yOffset, $otpFg, $fontFile, $char);
$startX += 42; // Spacing to next char (larger spacing to handle angle/size variations)
}
} else {
// Fallback: draw scaled bitmap fonts if TTF not supported
$otpWidth = 45; // 3 chars * 15px width roughly
$otpHeight = 20;
$otpIm = imagecreatetruecolor($otpWidth, $otpHeight);
$otpBg = imagecolorallocate($otpIm, 240, 244, 248);
$otpFgTemp = imagecolorallocate($otpIm, 13, 110, 253);
imagefill($otpIm, 0, 0, $otpBg);
$x = 2;
foreach ($chars as $char) {
$y = random_int(0, 5); // Slight vertical jitter
imagestring($otpIm, 5, $x, $y, $char, $otpFgTemp);
$x += 14;
}
// Scale it up by 3x onto the main image
$scale = 3;
$dstWidth = $otpWidth * $scale;
$dstHeight = $otpHeight * $scale;
$dstX = random_int(80, 140);
$dstY = random_int(30, 40);
imagecopyresampled($im, $otpIm, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $otpWidth, $otpHeight);
imagedestroy($otpIm);
}
// --- 2. Add Background Noise (Lines & Dots) ---
for ($i = 0; $i < 6; $i++) {
imageline($im, random_int(0, 300), random_int(0, 100), random_int(0, 300), random_int(0, 100), $noiseColor);
}
for ($i = 0; $i < 100; $i++) {
imagesetpixel($im, random_int(0, 300), random_int(0, 100), $noiseColor);
}
// --- 3. Draw Random Header Label with Variable Font ---
$labels = [
'Verification Code:',
'Your OTP:',
'Security Key:',
'Access Number:',
'Auth Code:',
'Login Pin:',
'Secret Key:',
'Your Number:',
'One Time Pass:',
'Code:'
];
$label = $labels[array_rand($labels)];
$labelFont = random_int(3, 5); // Random built-in font (3, 4, or 5)
imagestring($im, $labelFont, 20, 10, $label, $textColor);
// Draw a bounding border
imagerectangle($im, 0, 0, 299, 99, $noiseColor);
// Capture output
ob_start();
imagepng($im);
$imageData = ob_get_clean();
imagedestroy($im);
return base64_encode($imageData);
}
}