QiAds Web SDKدليل المطوّرين
سكربت واحد يعرض الإعلانات الحيّة على مواضع QiAds في أي صفحة أو WebView داخل تطبيق، ويقيس طلبات الإعلان ومرات العرض والمشاهدات الفعلية والنقرات بتعريفات MRC — دون ملفات تعريف ارتباط ودون هوية الزائر.
سطران للتركيب
وسم سكربت واحد وعنصر للخانة؛ لا مكتبات ولا خطوة بناء.
قياس وفق المعايير
طلبات الإعلان، مرات العرض، المشاهدة الفعلية (50% لثانية) والنقرة من الخادم.
الخصوصية أولاً
لا ملفات تعريف ارتباط ولا هوية زائر؛ أعداد مجمّعة لكل يوم بتوقيت بغداد.
جاهز للتطبيقات
أحداث للتطبيق المضيف وتسليم النقرات ودعم WebView على أندرويد وiOS.
التركيب
اختر أحد الشكلين. كود كل موضع بمقاسه موجود في «كود التضمين» داخل لوحة الإدارة.
دائم التحديثموصى به
أحدث إصدار من 1.x دائماً؛ يصل كل إصلاح خلال دقائق.
<script async src="https://qi-ads.com/qiads.js" data-site="superqi"></script>مثبّت الإصدار مع SRI/sdk/1.3.1/qiads.js
ملف لا يتغيّر أبداً، وبصمته من ملف الإصدار نفسه — لمن تشترط مراجعته الأمنية ذلك.
<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 قسماً
نظرة عامة
qiads.js سكربت واحد بلا اعتماديات يعرض الإعلانات الحيّة الآن على موضع من مواضع QiAds — بنراً متبدّلاً أو بطاقة داخل المحتوى أو نافذة منبثقة — في أي صفحة ويب أو داخل WebView في تطبيق، ويقيسها. يُقدَّم من الأصل نفسه الذي يقدّم الواجهة البرمجية للإعلانات: https://qi-ads.com.
ما الذي يقيسه (أعداد مجمّعة لكل يوم بتوقيت بغداد؛ التعريفات الكاملة في منهجية القياس):
- طلبات الإعلان ونسبة التعبئة: كل مرة تطلب فيها خانةٌ إعلاناتها، أي كل تحميل لشاشة فيها الخانة.
- مرات العرض: أُدرج الإعلان واكتمل تحميل صورته.
- المشاهدات الفعلية: ظهر 50% من الإعلان ثانية متواصلة (تعريف MRC).
- النقرات: يحتسبها خادم QiAds لحظة تحويل المستخدم إلى رابط المعلن.
- أجهزة فريدة يومياً (تقديري): عدد تقريبي للأجهزة، لا للأشخاص.
ما لا يفعله أبداً: لا يضع ملفات تعريف ارتباط ولا يقرؤها (استثناء واحد: حدّ تكرار النافذة المنبثقة في صفحة بلا تخزين محلي، انظر الخصوصية)، ولا معرّفات دائمة، ولا بصمة للجهاز، ولا أسماء أو أرقام هواتف أو حسابات، ولا يرسل الجزء بعد ? من عنوان الصفحة.
يُرسم كل إعلان داخل Shadow DOM (عنصر <qiads-slot> داخل الخانة، و<qiads-popup> للنافذة)، فلا تصل إليه أنماط صفحتك ولا يغيّر هو شيئاً فيها. في المحرّكات التي لا تدعم Shadow DOM يُرسم الإعلان مباشرة بأنماط محصورة في أصناف .qiads*.
البدء السريع
<!-- 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إلى عنوان الصفحة، فيُعرض كل شيء ولا يُحتسب شيء.
التثبيت
دائم التحديث (موصى به)
<script async src="https://qi-ads.com/qiads.js" data-site="superqi"></script>/qiads.js هو دائماً أحدث إصدار من 1.x. يُحفظ في الكاش 5 دقائق مع إعادة تحقق في الخلفية حتى يوم، فيصل كل إصلاح إلى كل صفحة خلال دقائق دون أن تغيّر شيئاً في كودك. لا يكسر أي إصدار من 1.x ما يعمل اليوم (انظر سياسة الإصدارات).
مثبّت الإصدار مع Subresource Integrity
<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".
داخل التطبيقات (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. الطريقة الصحيحة أن تستلم النقرة:
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): تلك ليست نقرة المستخدم، وتُصنَّف روبوتاً، ويخسر المستخدم صفحة الوجهة.
إن لم تمرّ النقرة عبر الجسر، فاعترض الانتقال في الطبقة الأصلية:
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
}
}// 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.
قائمة الفحص على أجهزة حقيقية
- نسخة الاختبار تحمل
data-test(أو?qiads_test=1): الإعلانات تظهر ولا يُحتسب شيء. - التشخيص:
?qiads_debug=1ثم Console عبرchrome://inspectأو Safari Web Inspector يعرض كل خطوة وحكم الخادمX-QiAds-Result. - الضغط على الإعلان يفتحه في Custom Tabs أو SFSafariViewController ويبقى التطبيق في مكانه؛ زر الرجوع يعيد إلى التطبيق.
- النافذة المنبثقة: زر الرجوع في Android يغلقها (اربطه بـ
QiAds.close()ما دامت مفتوحة). - تغطية WebView بشاشة أصلية توقف المشاهدات، والعودة تستأنفها.
- أرسل إلينا سلاسل User-Agent الحقيقية لـ WebView التطبيق في Android وiOS (نثبّتها في اختبارات الحركة غير الصالحة).
- تأكّد أن WebView بحجم الشاشة لا بارتفاع المحتوى.
التكامل الأصلي و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.
مرجع الإعدادات
خصائص وسم السكربت
| الخاصية | منذ | الافتراضي | الوصف |
|---|---|---|---|
data-host | 1.2.1 | أصل السكربت | أصل الواجهة البرمجية حين يُستضاف السكربت في مكان آخر |
data-site | 1.2.1 | فارغ | معرّف القناة: superqi أو gini؛ أي قيمة أخرى تُسجَّل «أخرى» (حتى 64 حرفاً) |
data-lang | 1.2.1 | <html lang> وإلا ar | لغة الإعلانات في الصفحة: ar أو en أو ku |
data-popup | 1.2.1 | — | يفتح نافذة منبثقة بعد التحميل. باقٍ للتوافق فقط؛ لا تستعمله — افتح النافذة عند نقطة انتقال طبيعية بـ QiAds.popup |
data-cap | 1.2.1 | 1/day | حدّ تكرار النافذة: 1/day أو 1/session أو always |
data-rotate | 1.2.1 | 7000 | الملّي ثانية بين الإبداعات؛ الحد الأدنى 3000 (القيمة الأصغر تُرفع إليه)، و0 يوقف التبديل |
data-test | 1.3.0 | معطّل | وضع الاختبار: يعرض كالمعتاد ولا يُحتسب شيء |
data-debug | 1.3.0 | معطّل | وضع التشخيص: رسائل console.info وحكم الخادم X-QiAds-Result |
data-observe | 1.3.0 | معطّل | يملأ عناصر [data-qiads] التي تُضاف إلى الصفحة لاحقاً (MutationObserver) |
data-page | 1.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-format | data-qiads-format | 1.2.1 | banner | banner بنر متبدّل · inline بطاقة · popup نافذة منبثقة |
data-limit | data-qiads-limit | 1.2.1 | 7 | أقصى عدد إبداعات يتبدّل بينها البنر، من 1 إلى 10. البطاقة والنافذة تعرضان إبداعاً واحداً يُختار عشوائياً |
data-rotate | data-qiads-rotate | 1.2.1 | من الوسم | مدة التبديل لهذه الخانة |
data-target | data-qiads-target | 1.2.1 | _blank | _blank أو _self. لا تستعمل _self داخل WebView تطبيق أبداً (النقرات) |
data-fallback | data-qiads-fallback | 1.2.1 | — | يُبقي محتوى العنصر الأصلي حين لا يوجد إعلان |
data-lang | — | 1.2.1 | من الوسم | لغة هذه الخانة: ar أو en أو ku |
data-dir | data-qiads-dir | 1.2.1 | حسب اللغة | rtl أو ltr أو auto |
data-cap | data-qiads-cap | 1.2.1 | من الوسم | حدّ تكرار خانة من نوع popup |
data-aspect | data-qiads-aspect | 1.3.0 | — | يحجز المساحة أثناء التحميل بنسبة W/H، مثل 9/4 |
data-collapse | data-qiads-collapse | 1.3.0 | auto | auto تُطوى الخانة الفارغة؛ never تبقى مساحتها المحجوزة |
data-page | data-qiads-page | 1.3.0 | من الوسم | اسم منطقي للصفحة لأحداث هذه الخانة |
الاسم البديل data-qiads-* يُقرأ قبل الاسم القصير، وهو مفيد حين يتعارض اسم قصير مع إطار عمل يستعمل data-format مثلاً. لا يوجد بديل لـ data-lang.
خصائص يكتبها السكربت (مخرجات)
| الخاصية | القيم |
|---|---|
data-qiads-state | loading ← 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 |
test | true يفعّل وضع الاختبار |
debug | true يفعّل وضع التشخيص |
onClick | دالة تُستدعى قبل كل انتقال بتفاصيل النقرة؛ إعادة false تلغي الانتقال |
الأولوية: window.qiadsConfig (يُقرأ عند التحميل) ← خصائص الوسم data-* تتقدّم عليه ← QiAds.init(options) يتقدّم على الاثنين من لحظة استدعائه. ويفعّل ?qiads_test=1 و?qiads_debug=1 في عنوان الصفحة الوضعين أيضاً.
واجهة JavaScript
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 — كل خيار يتقدّم على خاصية الخانة المقابلة.
طابور الأوامر يعمل قبل تحميل السكربت، ويُنفَّذ بعد اكتمال تحليل الصفحة:
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 ينفّذ الأمر فوراً. كل دالة تلتقط أخطاءها: خطأ في السكربت لا يصل إلى صفحتك.
الأحداث
كل حدث يصل بطريقتين بالتفاصيل نفسها: QiAds.on(type, fn)، وحدث DOM باسم qiads:<type> يصعد (bubbles) من عنصر الخانة (ومن document لنافذة فُتحت بلا خانة).
QiAds.on | حدث DOM | متى | قابل للإلغاء |
|---|---|---|---|
request | qiads:request | تطلب الخانة إعلاناتها من الخادم | لا |
render | qiads:render | أُدرج إبداع واكتملت صورته (مرة عرض) | لا |
empty | qiads:empty | لا إعلان لعرضه؛ reason: no_fill أو unknown_placement أو invalid_placement | لا |
error | qiads:error | تعذّر العرض؛ reason: timeout أو http أو rate_limited أو network أو images_failed | لا |
impression | qiads:impression | مشاهدة فعلية: 50% لثانية متواصلة | لا |
click | qiads:click | نقرة حقيقية من المستخدم، قبل الانتقال | نعم |
close | qiads: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() من سكربت) لا تنتقل ولا تطلق أحداثاً.
QiAds.on("empty", function (d) { document.querySelector("#promo").hidden = true })
QiAds.on("impression", function (d) { analytics.track("ad_viewable", { placement: d.placement }) })الصيغ والمقاسات وحجز المساحة
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" يُبقي الصندوق المحجوز بخلفية محايدة فلا يتحرك المحتوى تحته.
اللغات
لكل إبداع نسخة عربية دائماً، وقد يرفع المعلن معها نسخة إنجليزية أو كردية. رابط الإعلان واحد لكل اللغات. لغة الخانة أول قيمة معروفة من: 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 · ڕیکلام). تُحتسب الأرقام للإبداع أياً كانت اللغة.
منهجية القياس
المصطلحات من إرشادات 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.
الخصوصية والبيانات
| البيان | أين | المدة |
|---|---|---|
| نوع الحدث (مشاهدة أو نقرة) والإبداع والحجز والموضع والوقت | حدث مفرد | 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 أيام. التفاصيل في سياسة الخصوصية.
وضع الاختبار ووضع التشخيص
وضع الاختبار — 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)، وتحملها أيضاً ردود رابط النقر.
الكاش والإصدارات
| المسار | Cache-Control | ملاحظة |
|---|---|---|
/qiads.js | public, max-age=300, stale-while-revalidate=86400 | دائم التحديث: يصل الإصدار الجديد خلال دقائق |
/sdk/<version>/qiads.js | public, max-age=31536000, immutable | لا يتغيّر أبداً؛ صالح لـ SRI |
/api/ads/serve | public, 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.
استكشاف الأخطاء
| العَرَض | تحقّق من |
|---|---|
لا يظهر شيء، و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_failed | img-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 |
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.
دعم المتصفحات
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) ولا يفعل السكربت شيئاً آخر.
الواجهة البرمجية REST
كل النقاط عامة. serve وtrack وasset مفتوحة عبر CORS وتجيب OPTIONS؛ أما رابط النقر فيُفتح انتقالاً في المتصفح، لا بطلب fetch.
GET /api/ads/serve
| المعامل | الوصف |
|---|---|
placement | معرّف الموضع (مطلوب) |
limit | من 1 إلى 10، والافتراضي 7 |
lang | ar (الافتراضي) أو 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)
{
"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 تبيّن أيّها أُرسل.
سياسة الإصدارات
- SemVer: الإصدار الرئيسي (2.0) وحده يزيل أو يغيّر خاصية أو حدثاً أو دالة أو حقلاً؛ الإصدار الفرعي (1.4) يضيف؛ والتصحيح (1.3.1) يصلح بلا تغيير في الواجهة.
/qiads.jsيتبع أحدث1.x. كل إصدار مثبّت/sdk/<version>/qiads.jsيبقى منشوراً.- الخادم يقبل الصيغة 1 للإرسال إلى الأبد، وحقول
serveتُضاف ولا تُحذف؛ الحقل المهمل يبقى حتى الإصدار الرئيسي التالي، ويُعلن الإهمال في سجل التغييرات قبل ذلك بإصدار فرعي على الأقل. - كل تغيير مدوّن في سجل التغييرات.