156 lines
6.5 KiB
PHP
156 lines
6.5 KiB
PHP
<?php
|
||
/**
|
||
* sms/parser.php — تحليل نص الرسالة إلى نقطتين
|
||
* ─────────────────────────────────────────────────────────────
|
||
* الراكب يكتب بلغته لا بصيغة نموذج. لا يمكن فرض قالب صارم على من
|
||
* لجأ إلى الرسائل أصلاً لأنه لا يستطيع استعمال التطبيق — فالمحلّل
|
||
* يقبل ما يقبله المنطق، ويرد بسؤال واضح حين يعجز.
|
||
*
|
||
* الصيغ المدعومة:
|
||
* من الجامعة الاردنية الى المستشفى التخصصي
|
||
* من الجامعة للمستشفى
|
||
* الجامعة - المستشفى
|
||
* الجامعة > المستشفى
|
||
* من 31.95,35.91 الى المستشفى (إحداثيات مباشرة)
|
||
*/
|
||
|
||
require_once __DIR__ . '/helpers.php';
|
||
|
||
/**
|
||
* هل هذه الرسالة طلب رحلة أصلاً؟
|
||
*
|
||
* الويبهوك نفسه يستقبل إشعارات الدفع من البنوك، فلا بد من فرز قبل
|
||
* التحليل. طلب الرحلة يحوي فاصلاً بين نقطتين؛ إشعار الدفع يحوي مبلغاً
|
||
* وكلمات مالية.
|
||
*/
|
||
function smsLooksLikeRideRequest(string $body): bool
|
||
{
|
||
$b = smsNormalizeArabic($body);
|
||
|
||
// كلمات الإشعارات المالية — وجودها يرجّح أنها ليست طلب رحلة.
|
||
if (preg_match('/(حواله|حوالة|رصيد|محفظتك|دينار|جنيه|ليره|مزود الخدمه|Orange Money)/ui', $b)) {
|
||
return false;
|
||
}
|
||
|
||
// الأنماط على النص الموحَّد: «الى/إلى» صارتا «الي» بعد التوحيد.
|
||
return (bool) preg_match('/(^|\s)من\s+.+\s+(الي|علي|ل)\s*\S/u', $b)
|
||
|| (bool) preg_match('/\S\s+[-]\s+\S/u', $b)
|
||
|| (bool) preg_match('/\S\s*[<>→]\s*\S/u', $b);
|
||
}
|
||
|
||
/**
|
||
* يستخرج نصّي النقطتين. لا يلمس قاعدة البيانات — الفصل يجعل اختباره
|
||
* ممكناً بلا بيئة كاملة، وهذا المكوّن هو الأكثر عرضة للتعديل.
|
||
*
|
||
* @return array{start:string,end:string}|null
|
||
*/
|
||
function smsExtractEndpoints(string $body): ?array
|
||
{
|
||
$b = smsNormalizeArabic($body);
|
||
|
||
// ١) «من … إلى …» — الصيغة الأوضح، ونجرّبها أولاً.
|
||
//
|
||
// تنبيه: النص هنا **بعد** التوحيد، والتوحيد يحوّل ى إلى ي —
|
||
// فـ«الى» و«إلى» كلتاهما صارتا «الي». مطابقة «الى» الأصلية هنا
|
||
// تفشل دائماً، وهو ما كان يُسقط أشيع صيغة يكتبها الناس.
|
||
//
|
||
// و«ل» الملتصقة تُعالَج على حدة: «للمستشفى» = ل + المستشفى،
|
||
// فابتلاع اللامين معاً يترك «مستشفى» بلا تعريف ويُفشل المطابقة.
|
||
if (preg_match('/من\s+(.+?)\s+(الي|علي|ل)\s*(.+)$/u', $b, $m)) {
|
||
$start = trim($m[1]);
|
||
$end = trim($m[3]);
|
||
|
||
// «لل…» وصلت مقسومة: الفاصل «ل» والباقي يبدأ بـ«ل». نعيد
|
||
// بناء أداة التعريف بدل تركها مبتورة.
|
||
if ($m[2] === 'ل' && str_starts_with($end, 'ل')) {
|
||
$end = 'ا' . $end;
|
||
}
|
||
|
||
// «… لل الصويفية» بمسافة: يبقى «ال» معلّقاً وحده فنسقطه.
|
||
$end = preg_replace('/^ال\s+/u', '', $end) ?? $end;
|
||
|
||
if ($start !== '' && $end !== '') {
|
||
return ['start' => $start, 'end' => $end];
|
||
}
|
||
}
|
||
|
||
// ٢) فاصل رمزي: «الجامعة - المستشفى».
|
||
// ننتبه ألا نقطع إحداثيات سالبة أو تاريخاً — لذلك نشترط مسافة
|
||
// حول الشرطة، بخلاف < و > اللذين لا يلتبسان بشيء.
|
||
if (preg_match('/^(.+?)\s+[-]\s+(.+)$/u', $b, $m)
|
||
|| preg_match('/^(.+?)\s*[<>→]\s*(.+)$/u', $b, $m)) {
|
||
$start = trim($m[1]);
|
||
$end = trim($m[2]);
|
||
if ($start !== '' && $end !== '') {
|
||
return ['start' => $start, 'end' => $end];
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* يحوّل نصاً واحداً إلى نقطة: إمّا إحداثيات مكتوبة صراحةً، أو معلم
|
||
* نبحث عنه في جدول المعالم.
|
||
*
|
||
* @return array{lat:float,lng:float,name:string}|null
|
||
*/
|
||
function smsResolvePoint(PDO $con, string $text): ?array
|
||
{
|
||
$t = trim($text);
|
||
|
||
// إحداثيات صريحة — يرسلها من نسخ موقعه من تطبيق آخر.
|
||
if (preg_match('/^(-?\d{1,3}\.\d+)\s*,\s*(-?\d{1,3}\.\d+)$/', $t, $m)) {
|
||
$lat = (float) $m[1];
|
||
$lng = (float) $m[2];
|
||
if ($lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180) {
|
||
return ['lat' => $lat, 'lng' => $lng, 'name' => $t];
|
||
}
|
||
}
|
||
|
||
// كلمات حشو شائعة في بداية اسم المكان — إزالتها ترفع نسبة المطابقة.
|
||
$t = preg_replace('/^(هنا|موقعي|عند|قرب|جنب|حد|في)\s+/ui', '', $t) ?? $t;
|
||
|
||
return smsFindPlace($con, $t);
|
||
}
|
||
|
||
/**
|
||
* التحليل الكامل: نص خام ← نقطتان محلولتان.
|
||
*
|
||
* @return array{
|
||
* ok: bool, reason?: string,
|
||
* raw_start?: string, raw_end?: string,
|
||
* start?: array, end?: array
|
||
* }
|
||
*/
|
||
function smsParseRideRequest(PDO $con, string $body): array
|
||
{
|
||
$ends = smsExtractEndpoints($body);
|
||
if (!$ends) {
|
||
return ['ok' => false, 'reason' => 'format'];
|
||
}
|
||
|
||
$start = smsResolvePoint($con, $ends['start']);
|
||
$end = smsResolvePoint($con, $ends['end']);
|
||
|
||
// نفرّق بين «لم أفهم الصيغة» و«لم أجد المكان»: الرسالتان الراجعتان
|
||
// للراكب مختلفتان تماماً، والخلط بينهما يجعله يعيد نفس الخطأ.
|
||
if (!$start || !$end) {
|
||
return [
|
||
'ok' => false,
|
||
'reason' => 'place',
|
||
'raw_start' => $ends['start'],
|
||
'raw_end' => $ends['end'],
|
||
'missing' => !$start ? $ends['start'] : $ends['end'],
|
||
];
|
||
}
|
||
|
||
return [
|
||
'ok' => true,
|
||
'raw_start' => $ends['start'],
|
||
'raw_end' => $ends['end'],
|
||
'start' => $start,
|
||
'end' => $end,
|
||
];
|
||
}
|