QiAds
v1.3.1إصدار مستقر≈ 15 KB مضغوط

QiAds Web SDKدليل المطوّرين

سكربت واحد يعرض الإعلانات الحيّة على مواضع QiAds في أي صفحة أو WebView داخل تطبيق، ويقيس طلبات الإعلان ومرات العرض والمشاهدات الفعلية والنقرات بتعريفات MRC — دون ملفات تعريف ارتباط ودون هوية الزائر.

  • سطران للتركيب

    وسم سكربت واحد وعنصر للخانة؛ لا مكتبات ولا خطوة بناء.

  • قياس وفق المعايير

    طلبات الإعلان، مرات العرض، المشاهدة الفعلية (50% لثانية) والنقرة من الخادم.

  • الخصوصية أولاً

    لا ملفات تعريف ارتباط ولا هوية زائر؛ أعداد مجمّعة لكل يوم بتوقيت بغداد.

  • جاهز للتطبيقات

    أحداث للتطبيق المضيف وتسليم النقرات ودعم WebView على أندرويد وiOS.

التركيب

اختر أحد الشكلين. كود كل موضع بمقاسه موجود في «كود التضمين» داخل لوحة الإدارة.

دائم التحديثموصى به

أحدث إصدار من 1.x دائماً؛ يصل كل إصلاح خلال دقائق.

HTML
<script async src="https://qi-ads.com/qiads.js" data-site="superqi"></script>

مثبّت الإصدار مع SRI/sdk/1.3.1/qiads.js

ملف لا يتغيّر أبداً، وبصمته من ملف الإصدار نفسه — لمن تشترط مراجعته الأمنية ذلك.

HTML
<script async src="https://qi-ads.com/sdk/1.3.1/qiads.js"
        integrity="sha384-BWO68+7tX2tlBmx9ZTWFyWudoNINILOXW2YLBQndvFTmi/2GNrcDff5/ddQfMPRa"
        crossorigin="anonymous" data-site="superqi"></script>

data-site هو معرّف القناة: superqi أو gini.

المحتويات · 19 قسماً
1

نظرة عامة

qiads.js سكربت واحد بلا اعتماديات يعرض الإعلانات الحيّة الآن على موضع من مواضع QiAds — بنراً متبدّلاً أو بطاقة داخل المحتوى أو نافذة منبثقة — في أي صفحة ويب أو داخل WebView في تطبيق، ويقيسها. يُقدَّم من الأصل نفسه الذي يقدّم الواجهة البرمجية للإعلانات: https://qi-ads.com.

ما الذي يقيسه (أعداد مجمّعة لكل يوم بتوقيت بغداد؛ التعريفات الكاملة في منهجية القياس):

  • طلبات الإعلان ونسبة التعبئة: كل مرة تطلب فيها خانةٌ إعلاناتها، أي كل تحميل لشاشة فيها الخانة.
  • مرات العرض: أُدرج الإعلان واكتمل تحميل صورته.
  • المشاهدات الفعلية: ظهر 50% من الإعلان ثانية متواصلة (تعريف MRC).
  • النقرات: يحتسبها خادم QiAds لحظة تحويل المستخدم إلى رابط المعلن.
  • أجهزة فريدة يومياً (تقديري): عدد تقريبي للأجهزة، لا للأشخاص.

ما لا يفعله أبداً: لا يضع ملفات تعريف ارتباط ولا يقرؤها (استثناء واحد: حدّ تكرار النافذة المنبثقة في صفحة بلا تخزين محلي، انظر الخصوصية)، ولا معرّفات دائمة، ولا بصمة للجهاز، ولا أسماء أو أرقام هواتف أو حسابات، ولا يرسل الجزء بعد ? من عنوان الصفحة.

يُرسم كل إعلان داخل Shadow DOM (عنصر <qiads-slot> داخل الخانة، و<qiads-popup> للنافذة)، فلا تصل إليه أنماط صفحتك ولا يغيّر هو شيئاً فيها. في المحرّكات التي لا تدعم Shadow DOM يُرسم الإعلان مباشرة بأنماط محصورة في أصناف .qiads*.

2

البدء السريع

HTML
<!-- 1) مرة واحدة في الصفحة، قبل نهاية <body> -->
<script async src="https://qi-ads.com/qiads.js" data-site="superqi"></script>

<!-- 2) حيث تريد الإعلان: المساحة محجوزة قبل وصوله -->
<div data-qiads="superqi_homepage_slot" data-format="banner" data-aspect="9/4" style="aspect-ratio:9/4"></div>
  • data-qiads معرّف الموضع كما تعرضه لوحة الإدارة في «كود التضمين»، مثل superqi_homepage_slot لسلايد الصفحة الرئيسية في سوبر كي.
  • data-site معرّف القناة التي تعرض الإعلان: superqi أو gini. أي قيمة أخرى تُسجَّل في التقارير «أخرى».
  • style="aspect-ratio:9/4" وdata-aspect يحجزان مكان الإعلان بنسبة الموضع 9:4 (مقاسه 1080×480) فلا يقفز المحتوى حين يظهر.
  • لا يظهر إلا حجز حالته «تعمل الآن» وضمن أيامه وله صورة صالحة؛ وإلا تُطوى الخانة (انظر data-collapse).
  • قبل الإطلاق جرّب في وضع الاختبار: أضف ?qiads_test=1 إلى عنوان الصفحة، فيُعرض كل شيء ولا يُحتسب شيء.
3

التثبيت

دائم التحديث (موصى به)

HTML
<script async src="https://qi-ads.com/qiads.js" data-site="superqi"></script>

/qiads.js هو دائماً أحدث إصدار من 1.x. يُحفظ في الكاش 5 دقائق مع إعادة تحقق في الخلفية حتى يوم، فيصل كل إصلاح إلى كل صفحة خلال دقائق دون أن تغيّر شيئاً في كودك. لا يكسر أي إصدار من 1.x ما يعمل اليوم (انظر سياسة الإصدارات).

مثبّت الإصدار مع Subresource Integrity

HTML
<script async src="https://qi-ads.com/sdk/1.3.1/qiads.js"
        integrity="sha384-…" crossorigin="anonymous" data-site="superqi"></script>

/sdk/<الإصدار>/qiads.js لا يتغيّر أبداً (كاش سنة كاملة، immutable)، فيمكن تثبيته ببصمة SRI: يرفض المتصفح الملف إن تغيّر بايت واحد. قيمة integrity الدقيقة تُطبع في صفحة دليل المطوّرين وفي «كود التضمين» في لوحة الإدارة، من ملف الإصدار نفسه. الترقية قرار منك: غيّر الإصدار والبصمة معاً. crossorigin="anonymous" شرط لعمل SRI، والخادم يرسل Access-Control-Allow-Origin: * لهذه الملفات. النسخة السابقة باقية للرجوع إليها: /sdk/1.2.1/qiads.js.

التحميل

  • استعمل async دائماً. يمكن وضع الوسم في <head> أو قبل نهاية <body>؛ الأوامر التي تسبق تحميله تنتظر في طابور (واجهة JavaScript).
  • لتسريع أول إعلان: <link rel="preconnect" href="https://qi-ads.com" crossorigin>.
  • تكرار الوسم في الصفحة لا يضر: النسخة الثانية لا تبدأ SDK ثانياً.
  • إن استضفت الملف على أصل آخر فاضبط data-host="https://qi-ads.com".
4

داخل التطبيقات (WebView)

يعمل الوسم كما هو داخل Android WebView وiOS WKWebView، لكن على التطبيق المضيف ثلاثة أمور: أين تُفتح النقرة، ومتى تكون الشاشة ظاهرة فعلاً، وأي أصول يثق بها.

إعدادات WebView

  • في Android: settings.javaScriptEnabled = true وsettings.domStorageEnabled = true (يحفظ فيه حدّ تكرار النافذة المنبثقة؛ وبدونه يرجع السكربت إلى كوكي على نطاق صفحتك). حمّل الصفحة من عنوانها https:// عبر loadUrl، لا عبر loadData أو ملفات محلية، ليكون للصفحة أصل وعنوان صحيحان. لا حاجة إلى تفعيل كوكيز الطرف الثالث.
  • في iOS: إعدادات WKWebView الافتراضية كافية (JavaScript مفعّل). نفّذ مندوبي النقر أدناه.
  • إن كانت الصفحة محلية (file:// أو مخطط خاص) فضع data-host="https://qi-ads.com"، واستعمل data-page لاسم منطقي للشاشة مثل superqi/home.
  • إن حملت مسارات شاشاتك معرّفات (رقم هاتف أو حساب أو طلب، أو رمزاً) فاستعمل data-page باسم منطقي للشاشة أيضاً. الخادم يستبدل الأرقام الطويلة والرموز في المسار بـ :id، لكن الاسم المنطقي أوضح في التقارير ولا يحمل شيئاً عن المستخدم.

النقرات: سلّمها إلى التطبيق

لا تضع data-target="_self" داخل تطبيق أبداً: ستُفتح صفحة المعلن داخل WebView التطبيق نفسه، بجوار أي جسر برمجي (JS bridge) يكشفه التطبيق. ولا يكفي الافتراضي _blank: في Android (والإعداد الافتراضي setSupportMultipleWindows(false)) يتحوّل إلى انتقال يستبدل شاشتك، وفي WKWebView لا يحدث شيء ما لم يُنفَّذ createWebViewWith. الطريقة الصحيحة أن تستلم النقرة:

JS
document.addEventListener("qiads:click", function (event) {
  var bridge = window.SuperQiBridge                     // كائن الجسر في تطبيقك
  if (!bridge || !bridge.openExternal || !event.detail.url) return   // بلا جسر: يُفتح الرابط كالمعتاد
  event.preventDefault()                                // لا ينتقل السكربت بنفسه
  bridge.openExternal(event.detail.url)                 // يفتحه التطبيق في Custom Tabs أو SFSafariViewController
})

ألغِ النقرة فقط حين يوجد الجسر ورابط تسلّمه إليه: إلغاؤها ثم فشل استدعاء الجسر (صفحة فُتحت في متصفح عادي، أو نسخة أقدم من التطبيق) يضيّع النقرة، فلا انتقال ولا احتساب. أو عبر الإعداد: QiAds.init({ onClick: function (detail) { if (window.SuperQiBridge && detail.url) { SuperQiBridge.openExternal(detail.url); return false } } }).

detail.url هو رابط التحويل من QiAds (https://qi-ads.com/api/ads/click/…). افتحه في متصفح داخل التطبيق: Custom Tabs في Android وSFSafariViewController في iOS، فتُحتسب النقرة حين يتبع المتصفح التحويل إلى صفحة المعلن. لا تحلّ الرابط أولاً بعميل HTTP (OkHttp أو URLSession): تلك ليست نقرة المستخدم، وتُصنَّف روبوتاً، ويخسر المستخدم صفحة الوجهة.

إن لم تمرّ النقرة عبر الجسر، فاعترض الانتقال في الطبقة الأصلية:

KOTLIN
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url
        if (url.host == "your-own-host.example") return false   // صفحاتك تبقى في WebView
        CustomTabsIntent.Builder().build().launchUrl(view.context, url)
        return true
    }
}
SWIFT
// WKUIDelegate: روابط _blank
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
             for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    if let url = navigationAction.request.url { present(SFSafariViewController(url: url), animated: true) }
    return nil
}

// WKNavigationDelegate: كل نقرة على رابط خارجي
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
             decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if navigationAction.navigationType == .linkActivated,
       let url = navigationAction.request.url, url.host != "your-own-host.example" {
        present(SFSafariViewController(url: url), animated: true)
        decisionHandler(.cancel)
        return
    }
    decisionHandler(.allow)
}

قيّد الجسر البرمجي بأصولك أنت: WebViewCompat.addWebMessageListener مع allowedOriginRules في Android، والتحقق من message.frameInfo.securityOrigin في iOS. معاينة الرابط بالضغط المطوّل في iOS تفتح الرابط دون حدث نقر — ومع ذلك تُحتسب النقرة لأن التحويل يمرّ بخادم QiAds.

دورة الحياة: الظهور والتحديث

  • في Android: استدعِ webView.onPause() وwebView.onResume() مع النشاط؛ عندها تتبع document.visibilityState حالة الشاشة ولا يُقاس شيء وهي مخفية.
  • شاشة أصلية تُرسم فوق WebView يبقى ملحقاً بالنافذة لا تجعل الصفحة «مخفية». أخبر السكربت: QiAds.setVisible(false) حين تُغطّى، وQiAds.setVisible(true) حين تعود (عبر evaluateJavascript أو evaluateJavaScript). ما دامت false تتوقف عدادات المشاهدة والتبديل.
  • الشاشات الطويلة العمر: حين يعود المستخدم إلى شاشة باقية في الذاكرة (تبويب الرئيسية)، استدعِ QiAds.refresh() لتبدأ ظهورات جديدة بإعلانات محدّثة. يحدّث السكربت الخانات بنفسه بعد 30 دقيقة من الإخفاء وعند تغيّر اليوم بتوقيت بغداد.
  • التطبيقات أحادية الصفحة: بعد أن يرسم المسار خانات جديدة استدعِ QiAds.init()، أو أضف data-observe إلى الوسم ليملأها تلقائياً.

WebView بارتفاع محتواه (قيد معروف)

إن وُضع WebView بكامل ارتفاع محتواه داخل ScrollView أصلي، فلا تعرف الصفحة ما الظاهر على الشاشة: يرى IntersectionObserver كل خانة ظاهرة، فتُضخَّم المشاهدات الفعلية. اجعل WebView بحجم الشاشة يمرّر محتواه بنفسه، أو أخبرنا — واجهة يمرّر بها التطبيق موضع التمرير مدرجة في خارطة الطريق.

قوائم السماح وCSP

اسمح بـ https://qi-ads.com لـ: السكربت، والاتصال (serve وtrack)، والصور (الإبداعات)، والانتقال (رابط التحويل /api/ads/click/… الذي يرسل المستخدم بعده إلى نطاق المعلن). قوائم النطاقات في البرامج المصغّرة تحتاج الأصل نفسه. مثال CSP: script-src https://qi-ads.com; connect-src https://qi-ads.com; img-src https://qi-ads.com.

قائمة الفحص على أجهزة حقيقية

  1. نسخة الاختبار تحمل data-test (أو ?qiads_test=1): الإعلانات تظهر ولا يُحتسب شيء.
  2. التشخيص: ?qiads_debug=1 ثم Console عبر chrome://inspect أو Safari Web Inspector يعرض كل خطوة وحكم الخادم X-QiAds-Result.
  3. الضغط على الإعلان يفتحه في Custom Tabs أو SFSafariViewController ويبقى التطبيق في مكانه؛ زر الرجوع يعيد إلى التطبيق.
  4. النافذة المنبثقة: زر الرجوع في Android يغلقها (اربطه بـ QiAds.close() ما دامت مفتوحة).
  5. تغطية WebView بشاشة أصلية توقف المشاهدات، والعودة تستأنفها.
  6. أرسل إلينا سلاسل User-Agent الحقيقية لـ WebView التطبيق في Android وiOS (نثبّتها في اختبارات الحركة غير الصالحة).
  7. تأكّد أن WebView بحجم الشاشة لا بارتفاع المحتوى.
5

التكامل الأصلي وREST

للشاشات الأصلية التي لا WebView فيها استعمل الواجهة البرمجية REST:

  • اطلب GET /api/ads/serve واعرض الصورة، ثم أرسل الأحداث بـ POST /api/ads/track (الصيغة 2) من الجهاز نفسه وبـ User-Agent يحوي QiAdsNative/، مثل QiAdsNative/1.0 (android; app=superqi/5.2). عملاء HTTP العامّون (okhttp/4.12.0) يُصنَّفون روبوتات.
  • قاعدة المشاهدة التي تطبّقها بنفسك: 50% على الأقل من بكسلات الإعلان على الشاشة ثانية متواصلة والتطبيق في المقدّمة، مرة واحدة لكل معرّف ظهور (i: 12 حرفاً عشوائياً من [a-z0-9]، جديد لكل ظهور للشاشة × إبداع).
  • أرسل request (مع n)، وrender (مع w وh، وmb:1 حين تقيس الظهور)، وviewable. بلا render لا تدخل مشاهداتك نسبة المشاهدة الفعلية. لا ترسل click حين يحمل الإبداع click_href (تُستبعد not_navigation): افتح click_href في متصفح داخل التطبيق والتحويل يحتسبها.
  • لا تحلّ click_href بعميل HTTP قبل فتحه.
  • أثناء التطوير أرسل test:1.
6

مرجع الإعدادات

خصائص وسم السكربت

الخاصيةمنذالافتراضيالوصف
data-host1.2.1أصل السكربتأصل الواجهة البرمجية حين يُستضاف السكربت في مكان آخر
data-site1.2.1فارغمعرّف القناة: superqi أو gini؛ أي قيمة أخرى تُسجَّل «أخرى» (حتى 64 حرفاً)
data-lang1.2.1<html lang> وإلا arلغة الإعلانات في الصفحة: ar أو en أو ku
data-popup1.2.1—يفتح نافذة منبثقة بعد التحميل. باقٍ للتوافق فقط؛ لا تستعمله — افتح النافذة عند نقطة انتقال طبيعية بـ QiAds.popup
data-cap1.2.11/dayحدّ تكرار النافذة: 1/day أو 1/session أو always
data-rotate1.2.17000الملّي ثانية بين الإبداعات؛ الحد الأدنى 3000 (القيمة الأصغر تُرفع إليه)، و0 يوقف التبديل
data-test1.3.0معطّلوضع الاختبار: يعرض كالمعتاد ولا يُحتسب شيء
data-debug1.3.0معطّلوضع التشخيص: رسائل console.info وحكم الخادم X-QiAds-Result
data-observe1.3.0معطّليملأ عناصر [data-qiads] التي تُضاف إلى الصفحة لاحقاً (MutationObserver)
data-page1.3.0عنوان الصفحةاسم منطقي للصفحة يُرسل بدل عنوانها، مثل superqi/home (حروف لاتينية وأرقام و_-/.، حتى 128)

الخصائص المنطقية (data-test وdata-debug وdata-observe) تعمل بمجرد وجودها؛ القيم 0 وfalse وno وoff تطفئها.

خصائص الخانة

الخاصيةالاسم البديلمنذالافتراضيالوصف
data-qiads—1.2.1(مطلوب)معرّف الموضع [a-z0-9_]{2,64}
data-formatdata-qiads-format1.2.1bannerbanner بنر متبدّل · inline بطاقة · popup نافذة منبثقة
data-limitdata-qiads-limit1.2.17أقصى عدد إبداعات يتبدّل بينها البنر، من 1 إلى 10. البطاقة والنافذة تعرضان إبداعاً واحداً يُختار عشوائياً
data-rotatedata-qiads-rotate1.2.1من الوسممدة التبديل لهذه الخانة
data-targetdata-qiads-target1.2.1_blank_blank أو _self. لا تستعمل _self داخل WebView تطبيق أبداً (النقرات)
data-fallbackdata-qiads-fallback1.2.1—يُبقي محتوى العنصر الأصلي حين لا يوجد إعلان
data-lang—1.2.1من الوسملغة هذه الخانة: ar أو en أو ku
data-dirdata-qiads-dir1.2.1حسب اللغةrtl أو ltr أو auto
data-capdata-qiads-cap1.2.1من الوسمحدّ تكرار خانة من نوع popup
data-aspectdata-qiads-aspect1.3.0—يحجز المساحة أثناء التحميل بنسبة W/H، مثل 9/4
data-collapsedata-qiads-collapse1.3.0autoauto تُطوى الخانة الفارغة؛ never تبقى مساحتها المحجوزة
data-pagedata-qiads-page1.3.0من الوسماسم منطقي للصفحة لأحداث هذه الخانة

الاسم البديل data-qiads-* يُقرأ قبل الاسم القصير، وهو مفيد حين يتعارض اسم قصير مع إطار عمل يستعمل data-format مثلاً. لا يوجد بديل لـ data-lang.

خصائص يكتبها السكربت (مخرجات)

الخاصيةالقيم
data-qiads-stateloading ← ready أو empty أو error
data-qiads-langاللغة المعروضة فعلاً (ar أو en أو ku) حين ready

لا تضبطها بنفسك ولا تقرأها كمدخل: لهذا السبب لا يوجد الاسم البديل data-qiads-lang لـ data-lang — لو وُجد لقرأ السكربت لغته المعروضة (العربية الاحتياطية) عند التحديث وثبّتها.

window.qiadsConfig وQiAds.init(options)

الخيارالوصف
hostكـ data-host
siteكـ data-site
langكـ data-lang
testtrue يفعّل وضع الاختبار
debugtrue يفعّل وضع التشخيص
onClickدالة تُستدعى قبل كل انتقال بتفاصيل النقرة؛ إعادة false تلغي الانتقال

الأولوية: window.qiadsConfig (يُقرأ عند التحميل) ← خصائص الوسم data-* تتقدّم عليه ← QiAds.init(options) يتقدّم على الاثنين من لحظة استدعائه. ويفعّل ?qiads_test=1 و?qiads_debug=1 في عنوان الصفحة الوضعين أيضاً.

7

واجهة JavaScript

JS
QiAds.version                          // "1.3.1"
QiAds.config                           // الإعداد الحالي (للقراءة)
QiAds.init(options?)                   // يقرأ الإعداد ويملأ كل [data-qiads] جديد
QiAds.render(target, options?)         // Promise<boolean> — target: عنصر أو محدِّد CSS
QiAds.popup(placement, options?)       // Promise<boolean>
QiAds.close()                          // يغلق النافذة المفتوحة
QiAds.destroy(target?)                 // خانة واحدة، أو بلا وسيط: كل الخانات والنافذة
QiAds.on(type, fn)                     // يستمع لحدث (انظر الأحداث)
QiAds.off(type, fn)                    // يلغي الاستماع
QiAds.refresh(target?)                 // Promise<boolean> — ظهور جديد: معرّفات جديدة وإعلانات محدّثة
QiAds.setVisible(visible)              // ظهور التطبيق المضيف: false يوقف القياس والتبديل
QiAds.getSlots()                       // الخانات في الصفحة: {el, placement, format, state, lang, creatives}
QiAds.getVersion()                     // "1.3.1"

خيارات render: placement وformat وlimit وrotate وtarget وfallback وlang وdir وcap وaspect وcollapse وpage — كل خيار يتقدّم على خاصية الخانة المقابلة.

طابور الأوامر يعمل قبل تحميل السكربت، ويُنفَّذ بعد اكتمال تحليل الصفحة:

JS
window.qiads = window.qiads || []
qiads.push(["render", "#slot", { placement: "superqi_homepage_slot" }])
qiads.push(["setVisible", false])
qiads.push(function (api) { api.on("impression", function (d) { console.log(d.creative.id) }) })

// أو بأسلوب GPT:
window.QiAds = window.QiAds || { cmd: [] }
QiAds.cmd.push(function () { QiAds.refresh() })

كل أمر باسم دالته ثم وسائطها؛ والدالة في الطابور تُستدعى مع الواجهة. بعد التحميل يصبح window.qiads وQiAds.cmd كائنين لهما push ينفّذ الأمر فوراً. كل دالة تلتقط أخطاءها: خطأ في السكربت لا يصل إلى صفحتك.

8

الأحداث

كل حدث يصل بطريقتين بالتفاصيل نفسها: QiAds.on(type, fn)، وحدث DOM باسم qiads:<type> يصعد (bubbles) من عنصر الخانة (ومن document لنافذة فُتحت بلا خانة).

QiAds.onحدث DOMمتىقابل للإلغاء
requestqiads:requestتطلب الخانة إعلاناتها من الخادملا
renderqiads:renderأُدرج إبداع واكتملت صورته (مرة عرض)لا
emptyqiads:emptyلا إعلان لعرضه؛ reason: no_fill أو unknown_placement أو invalid_placementلا
errorqiads:errorتعذّر العرض؛ reason: timeout أو http أو rate_limited أو network أو images_failedلا
impressionqiads:impressionمشاهدة فعلية: 50% لثانية متواصلةلا
clickqiads:clickنقرة حقيقية من المستخدم، قبل الانتقالنعم
closeqiads:closeأُغلقت النافذة المنبثقةلا

الترتيب لإبداع واحد: request ← render ← impression، ثم click وclose حين تحدث؛ وempty أو error بدل render حين لا تُملأ الخانة.

التفاصيل (event.detail أو وسيط الدالة):

الحقلالوصف
typeاسم الحدث
placementمعرّف الموضع
creative{id, booking_id, lang} للإبداع المعني، أو null
impressionIdمعرّف الظهور (خانة واحدة × إبداع واحد)، أو null
urlالرابط الذي تفتحه النقرة: click_href مع معاملات السكربت، وإلا click_url
landingرابط المعلن نفسه (click_url)
slotعنصر الخانة، أو null لنافذة بلا خانة
reasonسبب empty وerror فقط

إلغاء النقرة يتم بأي من: event.preventDefault() على حدث DOM، أو detail.preventDefault()، أو إعادة false من مستمع on("click") أو من onClick. عندها لا ينتقل السكربت، ويفتح التطبيق detail.url بنفسه فتبقى النقرة محتسبة. النقرة التي لا يعلّمها المتصفح نقرة حقيقية (isTrusted = false، مثل el.click() من سكربت) لا تنتقل ولا تطلق أحداثاً.

JS
QiAds.on("empty", function (d) { document.querySelector("#promo").hidden = true })
QiAds.on("impression", function (d) { analytics.track("ad_viewable", { placement: d.placement }) })
9

الصيغ والمقاسات وحجز المساحة

  • banner: صورة بعرض الخانة. عند وجود أكثر من حملة حيّة يبدأ من إبداع عشوائي في كل تحميل ويتبدّل كل data-rotate، مع زر إيقاف وتشغيل ظاهر (WCAG 2.2.2) ونقاط تنقّل وسحب أفقي باللمس يراعي اتجاه اللغة. يتوقف التبديل عند التركيز بلوحة المفاتيح والمؤشر فوقه، وحين يخرج من الشاشة أو تُخفى الصفحة؛ لمسة عابرة لا توقفه. مع «تقليل الحركة» يستمر التبديل بلا مؤثرات انتقال. الإبداع الظاهر يُحمَّل فوراً والباقي لاحقاً.
  • inline: بطاقة تشبه الإشعار (صورة صغيرة وعنوان ونص)، بإبداع واحد.
  • popup: نافذة فوق الصفحة بزر إغلاق، تُغلق بـ Escape أو بالنقر خارجها، وتعيد التركيز إلى حيث كان. لا تُفتح إلا بعد تحميل صورتها. افتحها عند نقطة انتقال طبيعية — بعد إتمام عملية أو عند مغادرة شاشة — لا عند فتح التطبيق: qiads.push(["popup", "superqi_popup"])، وحدّ التكرار الافتراضي مرة في اليوم.

حجز المساحة (منع القفز): احجز صندوق الخانة قبل وصول الإعلان بنسبة الموضع. أبسط طريقة سمة style="aspect-ratio:9/4" على الخانة؛ وإن منعت سياسة CSP في صفحتك سمات style فضعها في ملف الأنماط: [data-qiads="superqi_homepage_slot"]{aspect-ratio:9/4}. وأضف data-aspect="9/4" ليحجز السكربت المساحة بنفسه في المحرّكات التي لا تدعم aspect-ratio (بديل padding-top). بعد ردّ الخادم يأخذ الصندوق نسبة الموضع الفعلية.

الطيّ: بلا إعلان تُخفى الخانة (display:none) افتراضياً. data-fallback يُبقي محتواها الأصلي (إعلان داخلي مثلاً)، وdata-collapse="never" يُبقي الصندوق المحجوز بخلفية محايدة فلا يتحرك المحتوى تحته.

10

اللغات

لكل إبداع نسخة عربية دائماً، وقد يرفع المعلن معها نسخة إنجليزية أو كردية. رابط الإعلان واحد لكل اللغات. لغة الخانة أول قيمة معروفة من: lang في QiAds.render ثم data-lang على الخانة؛ ثم data-lang على الوسم؛ ثم <html lang> (en* ← en · ku وckb* وkmr* ← ku · ar* ← ar)؛ وإلا ar. القيمة غير المعروفة تُتجاهل.

قاعدة الرجوع: تُعرض نسخة اللغة المطلوبة إن رُفعت لها صورة، وإلا النسخة العربية للحملة نفسها؛ لا تختفي حملة لأنها بلا ترجمة، ولا تُخلط لغتان في إعلان واحد. data-qiads-lang على الخانة وcreatives[].lang في ردّ serve يبيّنان اللغة المعروضة فعلاً. الاتجاه يتبع لغة ما يُعرض (rtl للعربية والكردية) ما لم تُضبط data-dir، وشارة «إعلان» تتبعها (إعلان · Ad · ڕیکلام). تُحتسب الأرقام للإبداع أياً كانت اللغة.

11

منهجية القياس

المصطلحات من إرشادات MRC وIAB، وهي النصوص نفسها التي تعرضها التقارير ولوحة الإدارة:

  • اليوم: كل الأرقام بأيام بغداد (UTC+3)، من منتصف الليل إلى منتصف الليل.
  • طلبات الإعلان: طلب إعلان: كل مرة تطلب فيها خانةٌ إعلاناتها من الخادم، أي كل تحميل لصفحة أو شاشة فيها الخانة. نسبة التعبئة = الطلبات التي عاد فيها إعلان واحد على الأقل ÷ كل الطلبات.
  • مرات العرض: مرة عرض: أُدرج الإعلان في الصفحة واكتمل تحميل صورته (بدء العرض، يحسبه المتصفح)، مرة واحدة لكل ظهور.
  • المشاهدات الفعلية: مشاهدة فعلية: ظهر 50% على الأقل من الإعلان ثانيةً متواصلة والصفحة ظاهرة وصورته محمّلة، بعد اكتمال ظهوره التدريجي، مرة واحدة لكل ظهور (تعريف MRC).
  • الظهور: الظهور: إعلان واحد في خانة واحدة منذ تحميلها. رجوع الإعلان نفسه في التدوير لا يُحسب من جديد، أما إعادة تحميل الخانة (تنقّل داخل التطبيق أو QiAds.refresh) فظهور جديد.
  • نسبة المشاهدة الفعلية: نسبة المشاهدة الفعلية = المشاهدات الفعلية التي قاسها الإصدار 1.3 فأحدث ÷ مرات العرض، على الأيام التي سُجّلت فيها مرات عرض فقط. «القياس غير مكتمل» يعني يوماً عُدّت فيه بعض المشاهدات بإصدار أقدم من 1.3 (لا يرسل مرات العرض، فلا تدخل مشاهداته النسبة)، أو يوماً قلّت فيه مرات العرض عن تلك المشاهدات فبقي خارج النسبة.
  • نسبة القياس: نسبة القياس = مرات العرض التي أمكن فيها قياس الظهور (المتصفح يدعم مراقبة ظهور العناصر وحالة الصفحة) ÷ كل مرات العرض.
  • النقرات: نقرة: تُحتسب على خادم QiAds لحظة تحويل المستخدم إلى رابط المعلن، مرة واحدة لكل جهاز وإعلان خلال 30 ثانية. مع إصدارات الكود الأقدم من 1.3 يُبلغ المتصفح عن النقرة بدلاً من ذلك.
  • معدل النقر: معدل النقر = النقرات ÷ المشاهدات الفعلية.
  • الوصول: أجهزة فريدة يومياً (تقديري): عدد تقريبي للأجهزة المختلفة في اليوم الواحد، من رمز مجزّأ لعنوان الشبكة ونوع المتصفح بمفتاح يومي يُحذف بعد يومين. الجهاز ليس شخصاً، والأجهزة التي تتشارك عنوان الشبكة وطراز الهاتف تُحسب جهازاً واحداً، فالرقم أقل من الحقيقي غالباً. لا تُجمع الأيام معاً.
  • الإجمالي والصافي: الصافي = ما احتُسب. الإجمالي = الصافي + الحركة غير الصالحة العامة (روبوتات، طلبات بلا هوية متصفح، متصفحات آلية، إعلانات بلا مساحة ظاهرة، تحميل مسبق، نقرات بلا انتقال إلى الرابط). حركة الاختبار والحجوزات غير الحيّة وتجاوز الحد اليومي للجهاز «مستبعد وليس احتيالاً» وتُعرض منفصلة.

تفاصيل القياس:

  • يُقاس الظهور بـ IntersectionObserver، وفي المحرّكات التي لا تدعمه بفحص موضع العنصر كل 100 ملّي ثانية بالقاعدة نفسها (ولا تُعدّ مرة العرض «مقيسة» بهذا الفحص إلا في النافذة العليا: داخل إطار من أصل آخر لا يرى الفحص شاشة الزائر). لا يُحتسب إعلان بلا مساحة، ولا عنصر مخفي (display:none أو visibility:hidden أو opacity:0)، ولا والصفحة مخفية أو QiAds.setVisible(false)، ولا قبل انتهاء ظهوره التدريجي (600 ملّي ثانية).
  • الحركة غير الصالحة العامة (GIVT) تُستبعد بهذه الأسباب: bot (روبوت أو زاحف معروف)، no_ua (بلا هوية متصفح)، automation (متصفح آلي، HeadlessChrome أو navigator.webdriver)، zero_size (إعلان مساحته بكسل واحد أو أقل)، prefetch (تحميل مسبق للرابط)، not_navigation (نقرة بلا انتقال إلى الرابط: رابط النقر حُمّل صورةً أو سكربتاً أو fetch أو إطاراً، أو بلغ عنها السكربت 1.3 لإعلان نقراته عبر التحويل). تطبيقات QiAdsNative/ معفاة من قواعد هوية المتصفح. تصفية مراكز البيانات مؤجلة.
  • مستبعد وليس احتيالاً: test (حركة اختبار)، not_live (حجز غير حيّ)، capped (تجاوز الحد اليومي للجهاز).
  • حدود يومية لكل جهاز وإبداع: 400 مشاهدة فعلية و30 نقرة و400 مرة عرض في اليوم (حدّ واحد للمشاهدة ومرة العرض، فلا يغيّر الحدّ نسبة المشاهدة)؛ ما زاد يُستبعد capped. والمكرّر لا يُحتسب: المشاهدة ومرة العرض مرة لكل ظهور خلال 30 دقيقة، والنقرة مرة لكل جهاز وإبداع خلال 30 ثانية.
  • الأحداث المتأخرة أكثر من 15 دقيقة عن لحظة إرسالها (WebView استيقظ بعد ساعات) لا تُحتسب، فلا تُنسب مشاهدات الأمس إلى اليوم.
  • لا تُخزَّن طلبات الإعلان ومرات العرض أحداثاً مفردة: هي عدّادات يومية فقط. ويمكن للخادم إيقاف إرسالها مؤقتاً عند الضغط عبر حقل measure في ردّ serve.
12

الخصوصية والبيانات

البيانأينالمدة
نوع الحدث (مشاهدة أو نقرة) والإبداع والحجز والموضع والوقتحدث مفرد30 يوماً
الموقع كما أُرسل (حتى 64 حرفاً)حدث مفرد30 يوماً؛ والتقارير تعرضه معرّف قناة أو «أخرى»
الصفحة: الأصل والمسار فقط، أو اسم منطقي؛ لا ? ولا # أبداً، والأرقام الطويلة والرموز في المسار تُستبدل بـ :idحدث مفرد30 يوماً
الصفحة المُحيلة: الأصل فقطحدث مفرد30 يوماً
هوية المتصفح User-Agent (حتى 512 حرفاً)حدث مفرد30 يوماً، لفحص الحركة غير الصالحة
رمز مجزّأ لعنوان الشبكة بملح عشوائي يومي (لا العنوان نفسه)حدث مفرد30 يوماً
إصدار السكربت ومصدر الحدثحدث مفرد30 يوماً
مفتاح يومي لكل جهاز وعدّاده (للوصول والحدود اليومية)جدول منفصليومان، ولا يُنسخ احتياطياً أبداً
الملح اليومي (32 بايت عشوائية)قاعدة البياناتيومان، ولا يُنسخ احتياطياً؛ قد تحتفظ ميزة Time Travel في D1 بنسخة حتى 30 يوماً
أعداد يومية: طلبات، تعبئة، مرات عرض، مشاهدات، نقرات، تقدير الأجهزة، الحركة المستبعدةمجاميعما دام الحجز قائماً، بلا أي بيان عن الزائر
وقت آخر ظهور للنافذة المنبثقة qiads_popup_<placement>متصفح الزائر، على نطاق صفحتكيوم أو جلسة

لا نجمع: عنوان IP نفسه، ولا ملفات تعريف ارتباط (الخادم لا يضع شيئاً ولا يقرأ شيئاً)، ولا أسماء أو هواتف أو حسابات، ولا معرّفات إعلانية، ولا الجزء بعد ? أو #، ولا الصفحة المُحيلة كاملة، ولا الموقع الجغرافي. حين يحذف الملح اليومي بعد يومين لا يمكن ربط رموز ذلك اليوم بعنوان. يحفظ السكربت في المتصفح وقت آخر نافذة منبثقة فقط، في localStorage أو sessionStorage، وإن كان التخزين معطّلاً ففي كوكي بالاسم نفسه على نطاق صفحتك. قد تحفظ سجلات تشغيل Cloudflare بيانات الطلب (العنوان وهوية المتصفح) حتى 7 أيام. التفاصيل في سياسة الخصوصية.

13

وضع الاختبار ووضع التشخيص

وضع الاختبار — data-test على الوسم، أو ?qiads_test=1 في عنوان الصفحة، أو QiAds.init({ test: true }): يعرض كل شيء ويطلق كل الأحداث، لكن الإرسال يحمل test:1 ورابط النقر يحمل t=1، فيُحوَّل المستخدم ولا يُحتسب شيء. صفحة المعاينة /ads-demo.html في وضع الاختبار دائماً.

وضع التشخيص — data-debug، أو ?qiads_debug=1، أو QiAds.init({ debug: true }): يكتب كل خطوة بـ console.info (يظهر في chrome://inspect وSafari Web Inspector)، ويحيط كل إعلان بإطار، ويرسل بـ fetch مع keepalive ليقرأ حكم الخادم من الترويسة X-QiAds-Result ويطبعه:

القيمةالمعنى
countedاحتُسب كل حدث
testحركة اختبار: لم يُخزَّن شيء
duplicateكل الأحداث مكرّرة
partialاحتُسب بعضها ولم يُحتسب بعضها
filtered:<reason>لم يُحتسب شيء؛ سبب أول حدث: bot أو no_ua أو automation أو zero_size أو prefetch أو not_navigation أو not_live أو capped أو rate_limited أو bad_token أو stale

الترويسة مكشوفة للقراءة من أي أصل (Access-Control-Expose-Headers)، وتحملها أيضاً ردود رابط النقر.

14

الكاش والإصدارات

المسارCache-Controlملاحظة
/qiads.jspublic, max-age=300, stale-while-revalidate=86400دائم التحديث: يصل الإصدار الجديد خلال دقائق
/sdk/<version>/qiads.jspublic, max-age=31536000, immutableلا يتغيّر أبداً؛ صالح لـ SRI
/api/ads/servepublic, max-age=60, s-maxage=120, stale-while-revalidate=300حملة جديدة أو منتهية تظهر خلال دقائق؛ ttl = 60
/api/ads/asset/…كاش عام نحو 5 دقائق مع ETagصورة الحملة المتوقفة تختفي من الكاش العام خلال دقائق
/api/ads/click/…no-storeكل نقرة تصل إلى الخادم

كل هذه المسارات مفتوحة عبر CORS (Access-Control-Allow-Origin: *) بلا بيانات اعتماد، وCross-Origin-Resource-Policy: cross-origin.

15

استكشاف الأخطاء

العَرَضتحقّق من
لا يظهر شيء، وdata-qiads-state="empty" مع no_fillحجز حالته «تعمل الآن»، واليوم ضمن أيامه، والقناة مفتوحة، وصورته صالحة. افتح /api/ads/serve?placement=… مباشرة
empty مع unknown_placementالمعرّف خاطئ أو الموضع موقوف
empty مع invalid_placementالمعرّف ليس [a-z0-9_]{2,64}؛ لا يُرسل أي طلب
error مع timeout أو network أو http أو rate_limitedالوصول إلى qi-ads.com، وconnect-src في CSP، وdata-host
error مع images_failedimg-src في CSP، أو صورة الإبداع غير متاحة (أبلغنا)
النقرة تفتح داخل التطبيق، أو لا يحدث شيء في iOSالنقرات: سلّمها إلى التطبيق
الأرقام لا تتحرك في اللوحةفعّل وضع التشخيص واقرأ X-QiAds-Result: test (هل وضع الاختبار مفعّل؟)، filtered:not_live، filtered:capped، filtered:bot (هوية المتصفح)
مشاهدات قليلة مقابل مرات العرضالخانة أسفل الشاشة، أو WebView مغطّى، أو تمرير سريع؛ المشاهدة تحتاج 50% لثانية
إعلان عربي رغم data-lang="en"قاعدة الرجوع: لا صورة إنجليزية للحملة
النافذة المنبثقة لا تفتححدّ التكرار: امسح qiads_popup_<placement> أو استعمل cap: "always" في الاختبار؛ وتأكد أن صورتها تُحمَّل
المحتوى يقفز حين يظهر الإعلانحجز المساحة
سياسة CSP صارمةCSP وTrusted Types وSRI
16

CSP وTrusted Types وSRI

  • أضف https://qi-ads.com إلى script-src وconnect-src وimg-src.
  • الأنماط تُطبَّق عبر adoptedStyleSheets وCSSOM، فلا حاجة إلى style-src 'unsafe-inline' في المحرّكات الحديثة (Safari 16.4 وChrome 73 وFirefox 101 فما فوق). في الأقدم يُدرج عنصر <style> داخل Shadow DOM.
  • لا innerHTML ولا eval ولا نص يُفسَّر كـ HTML: يعمل السكربت تحت require-trusted-types-for 'script'.
  • للتثبيت ببصمة: مثبّت الإصدار مع Subresource Integrity.
17

دعم المتصفحات

Android System WebView وChrome من 61، وiOS وiPadOS (Safari وWKWebView) من 12.2، والمتصفحات الحديثة على الحاسوب. ما يغيب في محرّك قديم له بديل: IntersectionObserver ← فحص كل 100 ملّي ثانية؛ aspect-ratio في CSS (قبل iOS 15 وWebView 88) ← padding-top؛ Shadow DOM ← أنماط محصورة؛ sendBeacon ← fetch مع keepalive. المحرّك الذي لا يدعم Promise تُطوى فيه الخانات (أو يبقى data-fallback) ولا يفعل السكربت شيئاً آخر.

18

الواجهة البرمجية REST

كل النقاط عامة. serve وtrack وasset مفتوحة عبر CORS وتجيب OPTIONS؛ أما رابط النقر فيُفتح انتقالاً في المتصفح، لا بطلب fetch.

GET /api/ads/serve

المعاملالوصف
placementمعرّف الموضع (مطلوب)
limitمن 1 إلى 10، والافتراضي 7
langar (الافتراضي) أو en أو ku
siteمعرّف القناة (لا يغيّر الرد)
svإصدار السكربت (لا يغيّر الرد)

الرد 200: {ok, lang, placement:{id, kind, name, aspect_ratio, width, height}, creatives:[{id, booking_id, lang, image, click_url, click_href, alt, title, body, width, height}], ttl, measure:{request, render}}. click_href رابط التحويل الموقّع (أو null لإبداع بلا رابط)، وbooking_id مهمل ويبقى حتى 2.0، وmeasure يقول أي عدّادين يرسلهما العميل. الأخطاء: 400 invalid_placement و404 unknown_placement و429 rate_limited (مع Retry-After: 60) و503 unavailable.

GET /api/ads/click/{token}

يحوّل (302) إلى رابط المعلن ويحتسب النقرة. الرابط من click_href كما هو، مع معاملات اختيارية يضيفها السكربت: s (الموقع) وpv وi (معرّف الظهور) وsv وt=1 (اختبار) وwd=1 (navigator.webdriver، يُحسب متصفحاً آلياً). الوجهة تُقرأ دائماً من قاعدة البيانات، لا من الرابط. صلاحية الرمز 48 ساعة؛ الرمز القديم يُحوَّل ولا يُحتسب. يعمل التحويل ما دام الحجز حيّاً وحتى يوم بعد انتهائه؛ ولا يعمل أبداً لحجز ملغى أو مرفوض (صفحة 410 محايدة). HEAD يجيب مثل GET ولا يُحتسب. التحميل المسبق (Sec-Purpose أو Purpose: prefetch أو prerender) لا يُحوَّل: يأخذ 503 فارغاً فيتركه المتصفح، وتُحتسب النقرة الحقيقية حين تصل. ولا تُحتسب إلا نقرة انتقل فيها المتصفح إلى الرابط (Sec-Fetch-Mode: navigate وSec-Fetch-Dest: document حين يرسلهما): رابط يُحمَّل صورةً أو سكربتاً أو بـfetch أو داخل إطار يُحوَّل ولا يُحتسب (not_navigation). الترويسات: Cache-Control: no-store وReferrer-Policy: strict-origin-when-cross-origin وX-QiAds-Result.

POST /api/ads/track — الصيغة 2 (من 1.3.0)

JSON
{
  "v": 2, "sv": "1.3.0", "site": "superqi", "page": "https://example.com/home", "ref": "https://example.com",
  "pv": "k3j9x0abq2m4z8c1", "now": 1791500000000,
  "events": [
    { "e": "request",  "p": "superqi_homepage_slot", "n": 3, "ts": 1791499999200 },
    { "e": "render",   "p": "superqi_homepage_slot", "m": "<material uuid>", "i": "a1b2c3d4e5f6", "w": 360, "h": 160, "mb": 1, "ts": 1791499999500 },
    { "e": "viewable", "p": "superqi_homepage_slot", "m": "<material uuid>", "i": "a1b2c3d4e5f6", "w": 360, "h": 160, "ts": 1791499999900 }
  ]
}

e واحد من request وrender وviewable وclick (الأخير فقط لإبداع بلا click_href). حتى 20 حدثاً و8 كيلوبايت في الإرسال الواحد؛ والسكربت يقسم الدفعات عند 3.5 كيلوبايت. test:1 للاختبار، وwd:1 حين navigator.webdriver. الحدث الذي يسبق now بأكثر من 15 دقيقة لا يُحتسب. يُرسل الجسم JSON بنوع text/plain (لا يحتاج preflight). الرد 204 مع X-QiAds-Result؛ 400 لطلب غير صالح، و413 لجسم أكبر من الحد، و429 عند تجاوز الحد، و503 إن تعذّرت القاعدة.

POST /api/ads/track — الصيغة 1 (حتى 1.2.1، مقبولة دائماً)

{v:1, event:"impression"|"click", material_id, booking_id, placement_id, site, page, ref, pv, ts} حتى 4 كيلوبايت. event: "impression" تعني مشاهدة فعلية. تُنظَّف page وref كما في الصيغة 2.

GET /api/ads/asset/{material_id}

صورة الإبداع، تُقدَّم فقط أثناء عمل الحجز، وإلا 404 (يُحفظ في كاش الشبكة دقيقة). ?lang=en أو ?lang=ku لصورة تلك اللغة، والعربية إن لم تُرفع؛ وContent-Language تبيّن أيّها أُرسل.

19

سياسة الإصدارات

  • SemVer: الإصدار الرئيسي (2.0) وحده يزيل أو يغيّر خاصية أو حدثاً أو دالة أو حقلاً؛ الإصدار الفرعي (1.4) يضيف؛ والتصحيح (1.3.1) يصلح بلا تغيير في الواجهة.
  • /qiads.js يتبع أحدث 1.x. كل إصدار مثبّت /sdk/<version>/qiads.js يبقى منشوراً.
  • الخادم يقبل الصيغة 1 للإرسال إلى الأبد، وحقول serve تُضاف ولا تُحذف؛ الحقل المهمل يبقى حتى الإصدار الرئيسي التالي، ويُعلن الإهمال في سجل التغييرات قبل ذلك بإصدار فرعي على الأقل.
  • كل تغيير مدوّن في سجل التغييرات.