Metadata-Version: 2.4
Name: alazoorims
Version: 1.0.0
Summary: Python client for the IMS SMS REST API — with auto pricing calculator and date convenience layer.
Author-email: Alazoor <akrmalazoor@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/alazoor/alazoorims
Project-URL: Documentation, https://github.com/alazoor/alazoorims#readme
Project-URL: Source, https://github.com/alazoor/alazoorims
Project-URL: Issues, https://github.com/alazoor/alazoorims/issues
Keywords: ims,sms,api,client,sms-gateway,otp,alazoor
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0.0,>=0.25.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: respx>=0.20; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Dynamic: license-file

# alazoorims

مكتبة Python للتعامل مع REST API لمنصة IMS SMS.

تغلّف المكتبة كل نقاط النهاية المتاحة في اللوحة، وتضيف طبقتين لتسهيل الاستخدام:

· حاسبة السعر التلقائية (Auto Calculator) — تحسب سعر العميل انطلاقاً من سعرك ونسبة ربحك.
· تسهيل التواريخ (Date Convenience) — تقبل صيغاً مبسّطة للتواريخ بدل كتابة ISO 8601 كاملاً.

---

الميزات

الميزة الوصف
تغطية كاملة كل نقاط النهاية: الرسائل، النطاقات، الأرقام، CDRs، العملاء، التعيين والإلغاء.
وضعان للبيانات dict (افتراضي) أو dataclasses (as_dataclass=True) مع Decimal للأموال.
حاسبة تلقائية auto_margin_percent لحساب client_payout_rate تلقائياً بتقريب آمن للأعلى.
تسهيل التواريخ "01", "08-01", "2026-08-01", "today", "7d", datetime, date.
احترام حدود المعدل إعادة محاولة تلقائية على 429 باستخدام Retry-After.
ترقيم تلقائي iter_all() يجلب كل الصفحات دون إدارة nextCursor يدوياً.
أخطاء واضحة هرمية استثناءات كاملة تربط أكواد أخطاء الـ API بأسماء Python.
توقيت UTC صارم كل حسابات التواريخ بتوقيت غرينتش، لا علاقة بتوقيت جهازك.

---

التثبيت

```bash
pip install alazoorims
```

لبيئة التطوير (اختبارات + أدوات):

```bash
pip install "alazoorims[dev]"
```

المتطلب الوحيد: Python 3.9 أو أحدث.

---

البدء السريع

```python
from alazoorims import IMSClient

# 1) أنشئ العميل — يقرأ التوكن من متغير البيئة IMS_API_TOKEN
client = IMSClient()

# 2) آخر 10 رسائل
messages = client.messages.list(limit=10)
print(messages["count"])

# 3) آخر 7 أيام من سجلات الفواتير (CDRs)
cdrs = client.cdrs.list(from_="7d")

# 4) كل أرقامك (تلقائياً عبر كل الصفحات)
for number in client.numbers.iter_all(as_dataclass=True):
    print(number.number, number.range, number.payout_rate)

# 5) عيّن رقماً لعميل مع حساب السعر تلقائياً بنسبة ربح 20%
client2 = IMSClient(auto_margin_percent=20)
client2.numbers.assign(client="acme_uk", numbers=["447700900123"])
```

---

المصادقة

هناك طريقتان لتمرير التوكن:

1) عبر متغير البيئة (الأفضل):

```bash
export IMS_API_TOKEN="your-token-here"
```

```python
client = IMSClient()  # يقرأ IMS_API_TOKEN تلقائياً
```

2) مباشرة في الكود (للسكربتات السريعة):

```python
client = IMSClient(token="your-token-here")
```

تحذير أمني: لا تضع التوكن في الكود المرفوع إلى Git، ولا تشاركه في محادثة أو تذكرة دعم. التوكن يمنح نفس صلاحيات حسابك بالكامل.

تخصيص العنوان والسلوك:

```python
client = IMSClient(
    token="...",
    base_url="https://imssms.org",  # عنوان لوحتك
    timeout=30.0,                    # ثوان لكل طلب
    max_retries=3,                   # محاولات إعادة عند 429/الشبكة
)
```

استخدم الكائن كـ Context Manager لضمان إغلاق الاتصال:

```python
with IMSClient() as client:
    client.messages.list()
```

---

الموارد

الرسائل — client.messages

```python
# آخر 10 رسائل (افتراضي)
client.messages.list()

# فلترة بفترة (استخدم صيغ التاريخ المبسطة)
client.messages.list(from_="2026-08-01", to="2026-08-31", limit=100)

# فلترة برقم مستقبِل أو مُرسِل
client.messages.list(number="447700900123", cli="WhatsApp")

# وضع dataclass
for msg in client.messages.list(limit=50, as_dataclass=True):
    print(msg.time, msg.number, msg.payout, msg.status)
```

المعاملات: from_, to, number, cli, limit, as_dataclass.

ملاحظة: بدون أي فلترة، الـ API يعيد آخر 10 رسائل فقط.

حالات الرسالة: pending, cleared, inactive, cli_blocked, cli_limit, range_limit, number_daily, number_weekly. أي حالة غير pending أو cleared تعني أن الرسالة لم تدر شيئاً.

---

النطاقات — client.ranges

```python
for rng in client.ranges.list(as_dataclass=True):
    print(rng.name, rng.prefix, rng.currency, rng.active)
    for rate in rng.rates:
        print(f"  cycle={rate.invoice_cycle_days}d "
              f"delay={rate.payout_delay_days}d "
              f"payout={rate.payout_rate}")
```

ملاحظات:

· rates هي أسعارك أنت فقط — أسعار المنبع لا تُكشف أبداً.
· خطط لا تملك أرقاماً عليها تظهر بسعر 0.
· unassignedNumbers تظهر لحسابات الوكلاء فقط.
· نقطة النهاية تتطلب Extended API.

---

الأرقام — client.numbers

عرض الصفحات:

```python
# صفحة واحدة (أنت تدير المؤشر)
page = client.numbers.list(limit=100)
print(page["count"], page.get("nextCursor"))

# كل الصفحات تلقائياً
for num in client.numbers.iter_all(limit=500, as_dataclass=True):
    print(num.number, num.client)

# بحث بجزء من الرقم
client.numbers.list(search="4477")
```

المعاملات: range_id, assigned, client_id, search, after, limit, as_dataclass.

تعيين أرقام لعميل (وكلاء فقط):

```python
# بسعر يدوي
client.numbers.assign(
    client="acme_uk",
    numbers=["447700900123", "447700900124"],
    client_payout_rate="0.0100",
)

# بحساب تلقائي (انظر قسم الحاسبة)
client.numbers.assign(
    client="acme_uk",
    numbers=["447700900123"],
    auto_margin_percent=20,
)
```

إلغاء التعيين:

```python
client.numbers.unassign(numbers=["447700900123"])
```

ملاحظات مهمة:

· client_payout_rate مطلوب إن لم تُفعّل الحاسبة. "لا يربح شيئاً" يُكتب "0".
· الحد الأقصى 1000 رقم لكل نداء.
· الأرقام المعيّنة لعميل آخر تُنقل تلقائياً وتُحتسب في reassigned.
· الإلغاء لا يسترجع أرباحاً سابقة.
· لا يمكن إضافة أرقام جديدة عبر الـ API — تُضاف من اللوحة أو بواسطة مشغّل المنصة.

---

سجلات الفواتير — client.cdrs

أكثر تفصيلاً من /messages: يحمل بادئة النطاق، العملة، ربحك، وربح العميل (للوكلاء)، ووقت التسوية.

```python
# آخر 7 أيام
client.cdrs.list(from_="7d")

# شهر كامل
client.cdrs.list(from_="2026-08-01", to="2026-08-31", limit=100)

# تصفية بنطاق محدد + وضع dataclass
for cdr in client.cdrs.list(
    from_="2026-08-01",
    to="2026-08-31",
    range_id="0f8b4a1e-6c2d-4a77-9d31-2b5e8c9a4f10",
    as_dataclass=True,
):
    print(cdr.number, cdr.payout, cdr.client_payout, cdr.cleared_at)
```

ملاحظات:

· clientPayout تظهر لحسابات الوكلاء فقط.
· clearedAt تكون None حتى تُسوَّى الفاتورة.
· تتطلب Extended API.

---

العملاء — client.clients

للحسابات الوكيلة فقط. تتطلب Extended API.

```python
# عرض العملاء
for cli in client.clients.list(as_dataclass=True):
    print(cli.username, cli.name, cli.can_use_api, cli.disabled)

# إنشاء عميل — توليد كلمة مرور تلقائياً
created = client.clients.create(
    username="acme_uk",
    name="Acme UK",
    email="ops@acme.example",
    can_use_api=True,
)
print(created["password"])  # تظهر مرة واحدة فقط!

# إنشاء عميل بكلمة مرور تختارها (12 حرفاً على الأقل)
client.clients.create(
    username="acme_uk_2",
    password="a-very-long-password-123",
    can_use_api=True,
    can_view_ranges=True,
)
```

مهم: إذا حذفت password، يولّد الـ API واحداً ويعيده مرة واحدة. احفظه فوراً — لا يمكن استرجاعه لاحقاً.

المعاملات: username, password, name, email, contact, can_use_api, can_view_ranges, can_use_extended_api.

---

الحاسبة التلقائية (Auto Calculator)

بدل كتابة سعر العميل يدوياً، دَع المكتبة تقرأ payoutRate الخاص بكل رقم وتطبّق نسبتك:

```
client_rate = agent_rate x (1 - margin / 100)
```

التقريب دائماً للأعلى إلى 4 خانات عشرية باستخدام Decimal (لتفادي فقدان الدقة).

مثال

سعرك (Agent Rate) نسبتك (Margin) سعر العميل
0.0125 20% 0.0100
0.0125 7% 0.0117 (تقريب للأعلى من 0.011625)
0.0100 20% 0.0080

ثلاث طرق للاستخدام

1) على مستوى العميل (تُطبَّق على كل assign):

```python
client = IMSClient(auto_margin_percent=20)
client.numbers.assign(client="acme_uk", numbers=["447700900123"])
# clientPayoutRate = "0.0100"
```

2) على مستوى النداء (يتجاوز الافتراضي مع تحذير):

```python
client = IMSClient(auto_margin_percent=50)
client.numbers.assign(
    client="acme_uk",
    numbers=["447700900123"],
    auto_margin_percent=20,  # يفوز + warnings.warn
)
```

3) دالة مستقلة بدون اتصال بالـ API:

```python
from alazoorims import calculate_client_rate

calculate_client_rate("0.0125", margin_percent=20)  # "0.0100"
calculate_client_rate("0.0125", margin_percent=7)   # "0.0117"
calculate_client_rate("0.0100", margin_percent=20)  # "0.0080"
```

قواعد الأمان

· لا يمكن تمرير client_payout_rate وauto_margin_percent في نفس النداء -> ValueError.
· إذا فُعّلت الحاسبة العامة ومرّرت نسبة محلية -> المحلية تفوز + تحذير.
· إذا لم تُفعّل الحاسبة ولم تمرّر client_payout_rate -> ValueError.
· النسبة يجب أن تكون بين 0 و100 (حصرياً).
· agent_rate يجب أن يكون موجباً.
· النتيجة النهائية لا تتجاوز أبداً agent_rate.

---

تسهيل التواريخ (Date Convenience)

بدل كتابة ISO 8601 كاملاً، تقبل المكتبة صيغاً مبسّطة وتُكمل الوقت تلقائياً.

الصيغ المدعومة

المدخل التفسير
"2026-08-01T00:00:00Z" ISO كامل — يُمرَّر كما هو
"2026-08-01T14:30" ISO بدون Z — يُفترض UTC
"2026-08-01 14:30" نفس السابق مع مسافة
"2026-08-01" تاريخ كامل — يُطبَّق حد اليوم
"08-01" شهر-يوم من السنة الحالية (UTC)
"01" يوم من الشهر الحالي (UTC)
"today" اليوم UTC
"yesterday" أمس UTC
"7d" آخر 7 أيام (من الآن - 7 أيام حتى الآن)
datetime(...) كائن Python
date(...) كائن Python
None لا فلترة

قاعدة حدّ اليوم

المعامل الوقت الافتراضي المضاف
from_ T00:00:00Z
to T23:59:59Z

أمثلة

كل السطور التالية متكافئة (بفرض اليوم 2026-09-18 UTC):

```python
client.messages.list(from_="01", to="18")
client.messages.list(from_="09-01", to="09-18")
client.messages.list(from_="2026-09-01", to="2026-09-18")
client.messages.list(from_="2026-09-01T00:00:00Z",
                    to="2026-09-18T23:59:59Z")
```

```python
from datetime import date, datetime

client.cdrs.list(from_=date(2026, 8, 1), to=date(2026, 8, 31))
client.cdrs.list(from_=datetime(2026, 8, 1, 12, 0))
client.cdrs.list(from_="7d")
client.messages.list(from_="today", to="today")
```

التوقيت: UTC فقط

الشهر والسنة الحاليان يُحسبان من datetime.now(timezone.utc) — بتوقيت غرينتش، لا بتوقيت جهازك. إذا كنت في اليمن (UTC+3) ونفّذت السكربت الساعة 02:00 صباحاً، المكتبة تعرف أن اليوم بتوقيت UTC هو اليوم السابق.

استخدام الدالة مباشرة

```python
from alazoorims.utils import normalize_datetime

normalize_datetime("01")                    # "2026-09-01T00:00:00Z"
normalize_datetime("01", end_of_day=True)   # "2026-09-01T23:59:59Z"
normalize_datetime("7d")                    # "2026-09-11T14:22:00Z"
normalize_datetime("today")                 # "2026-09-18T00:00:00Z"
```

الأخطاء

· تاريخ غير موجود ("02-30", "32") -> ValueError برسالة واضحة.
· صيغة غير معروفة -> ValueError مع قائمة الصيغ المدعومة.

---

الأخطاء

كل استثناء يورث من IMSAPIError، فـ except IMSAPIError يمسك كل شيء.

```
IMSAPIError (القاعدة)
├── IMSAuthError          <- missing_token / invalid_token (401)
├── IMSPermissionError    <- module_disabled / extended_api_disabled (403)
├── IMSRateLimitError     <- rate_limited (429) + retry_after
├── IMSValidationError    <- invalid_request / too_many_numbers / ...
├── IMSNotFoundError      <- not_found (404)
├── IMSServerError        <- internal_error (500) + error_id
└── IMSNetworkError       <- أخطاء الشبكة (timeout, DNS, connection)
```

مثال

```python
from alazoorims import (
    IMSAPIError,
    IMSAuthError,
    IMSPermissionError,
    IMSRateLimitError,
    IMSServerError,
    IMSValidationError,
)

try:
    client.numbers.assign(
        client="acme_uk",
        numbers=["447700900123"],
        client_payout_rate="0.0100",
    )
except IMSAuthError:
    print("التوكن غير صالح.")
except IMSPermissionError as exc:
    print(f"الميزة غير مفعّلة: {exc.error_code}")
except IMSValidationError as exc:
    print(f"طلب مرفوض: {exc.error_code}")
except IMSRateLimitError as exc:
    print(f"تجاوزت الحد — أعد المحاولة بعد {exc.retry_after} ثانية")
except IMSServerError as exc:
    print(f"خطأ من الخادم — error_id={exc.error_id}")
except IMSAPIError as exc:
    print(f"خطأ آخر: {exc.error_code}  payload={exc.payload}")
```

---

حدود المعدل (Rate Limits)

الـ API يفرض نافذتين لكل رمز:

· قصيرة: طلب واحد كل ثانية.
· طويلة: 40 طلباً كل دقيقة.

القيد يُحتسب لكل نقطة نهاية على حدة، فلا يؤثر messages على numbers.

المكتبة تتعامل مع 429 تلقائياً:

1. تقرأ Retry-After من الترويسة أو retryAfterSeconds من الجسم.
2. تنتظر المدة.
3. تعيد المحاولة حتى max_retries (افتراضياً 3).

إن نفدت المحاولات، يرفع IMSRateLimitError مع retry_after.

---

الأمثلة

مجلد Examples/ يحتوي 12 ملفاً جاهزاً للتشغيل:

# الملف الموضوع
01 01_messages.py قراءة الرسائل
02 02_ranges.py النطاقات والأسعار
03 03_numbers_list.py عرض الأرقام + الترقيم التلقائي
04 04_numbers_assign.py تعيين بسعر يدوي
05 05_numbers_assign_auto.py الحاسبة التلقائية
06 06_numbers_unassign.py إلغاء التعيين
07 07_cdrs.py سجلات الفواتير
08 08_clients.py إدارة العملاء
09 09_dataclass_mode.py وضع dataclass في كل الموارد
10 10_error_handling.py معالجة الأخطاء
11 11_date_convenience.py صيغ التواريخ
12 12_date_shortcuts.py today / yesterday / Nd

قبل التشغيل:

```bash
export IMS_API_TOKEN="your-token-here"
python Examples/01_messages.py
```

---

الترخيص

MIT — انظر ملف LICENSE.
