Skip to content

Commit

Permalink
Dailymotion Bid Adapter: accept ortb2 field (prebid#11366)
Browse files Browse the repository at this point in the history
* Dailymotion Bid Adaptor: initial release

* .md file lint issue resolved

* Dailymotion Bid Adaptor: build bidder request based on param with fallbacks

* Dailymotion Bid Adaptor: support video metadata

* Dailymotion Bid Adaptor: add support for sending adUnitCode

* Dailymotion Bid Adaptor: add support for sending startDelay

* feat(LEO-528): Allow multiple IAB categories in video metadata

The same way as we can have an array of IAB categories level 1 in the ORTB request, this PR introduces an array for the IAB categories level 2.

To be forward compatible with level [2.2](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.2.tsv) and [3.0](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%203.0.tsv) specifications, the category IDs should be sent as strings.

* Dailymotion bid adapter: Clarify the video metadata to provide in each player context

* Dailymotion bid adapter: Move API key to bid params

* Dailymotion bid adapter: Verify API key is string

Co-authored-by: Rumesh <[email protected]>

* Dailymotion bid adapter: Move API key to bid params (fix tests)

* Dailymotion Bid Adaptor: add gpp support and get coppa from request

* Dailymotion Bid Adaptor: fix lint error

* Dailymotion Bid Adaptor: add iabcat1 and fallback to ortb2 for iabcat2

* Dailymotion Bid Adaptor: get iabcats from ortb2.site.content.data

* Dailymotion Bid Adaptor: get content data from ortb2.site.content

* Dailymotion Bid Adaptor: add support for iabcat1 in videoParams and document mapping of ortb2 fpd to video metadata

* Dailymotion Bid Adaptor: add support for Object: App

* Dailymotion Bid Adaptor: only support video adunits in context instream

* Dailymotion bid adapter: Add standard ORTB video parameters

Note: I changed the case of `startdelay` to be consistent with the other parameters, but it won't have any impact on current deployments as this parameter is not used onsite.

* Dailymotion Bid Adaptor: add support for livestream and app

* Dailymotion Bid Adaptor: drop support for non standard fields in mediaTypes.video

* Dailymotion Bid Adaptor: update docs examples

---------

Co-authored-by: Kevin Siow <[email protected]>
Co-authored-by: Aditi Chaudhary <[email protected]>
Co-authored-by: Kevin Siow <[email protected]>
Co-authored-by: Rumesh <[email protected]>
  • Loading branch information
5 people authored and mefjush committed May 21, 2024
1 parent f6aef17 commit 6be8c3b
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 89 deletions.
105 changes: 74 additions & 31 deletions modules/dailymotionBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,59 @@ import { deepAccess } from '../src/utils.js';
* @return video metadata
*/
function getVideoMetadata(bidRequest, bidderRequest) {
const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {});
const videoBidderParams = deepAccess(bidRequest, 'params.video', {});
const videoParams = deepAccess(bidRequest, 'params.video', {});

const videoParams = {
...videoAdUnit,
...videoBidderParams, // Bidder Specific overrides
};
// As per oRTB 2.5 spec, "A bid request must not contain both an App and a Site object."
// See section 3.2.14
// Content object is either from Object: Site or Object: App
const contentObj = deepAccess(bidderRequest, 'ortb2.site')
? deepAccess(bidderRequest, 'ortb2.site.content')
: deepAccess(bidderRequest, 'ortb2.app.content');

// Store as object keys to ensure uniqueness
const iabcat1 = {};
const iabcat2 = {};
const parsedContentData = {
// Store as object keys to ensure uniqueness
iabcat1: {},
iabcat2: {},
};

deepAccess(bidderRequest, 'ortb2.site.content.data', []).forEach((data) => {
deepAccess(contentObj, 'data', []).forEach((data) => {
if ([4, 5, 6, 7].includes(data?.ext?.segtax)) {
(Array.isArray(data.segment) ? data.segment : []).forEach((segment) => {
if (typeof segment.id === 'string') {
// See https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy
// Only take IAB cats of taxonomy V1
if (data.ext.segtax === 4) iabcat1[segment.id] = 1;
// Only take IAB cats of taxonomy V2 or higher
if ([5, 6, 7].includes(data.ext.segtax)) iabcat2[segment.id] = 1;
if (data.ext.segtax === 4) {
parsedContentData.iabcat1[segment.id] = 1;
} else {
// Only take IAB cats of taxonomy V2 or higher
parsedContentData.iabcat2[segment.id] = 1;
}
}
});
}
});

const videoMetadata = {
description: videoParams.description || '',
duration: videoParams.duration || 0,
iabcat1: Object.keys(iabcat1),
duration: videoParams.duration || deepAccess(contentObj, 'len', 0),
iabcat1: Array.isArray(videoParams.iabcat1)
? videoParams.iabcat1
: Array.isArray(deepAccess(contentObj, 'cat'))
? contentObj.cat
: Object.keys(parsedContentData.iabcat1),
iabcat2: Array.isArray(videoParams.iabcat2)
? videoParams.iabcat2
: Object.keys(iabcat2),
id: videoParams.id || '',
lang: videoParams.lang || '',
: Object.keys(parsedContentData.iabcat2),
id: videoParams.id || deepAccess(contentObj, 'id', ''),
lang: videoParams.lang || deepAccess(contentObj, 'language', ''),
private: videoParams.private || false,
tags: videoParams.tags || '',
title: videoParams.title || '',
tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''),
title: videoParams.title || deepAccess(contentObj, 'title', ''),
topics: videoParams.topics || '',
xid: videoParams.xid || '',
livestream: typeof videoParams.livestream === 'number'
? !!videoParams.livestream
: !!deepAccess(contentObj, 'livestream', 0),
};

return videoMetadata;
Expand All @@ -67,7 +80,24 @@ export const spec = {
* @return boolean True if this is a valid bid, and false otherwise.
*/
isBidRequestValid: function (bid) {
return typeof bid?.params?.apiKey === 'string' && bid.params.apiKey.length > 10;
if (bid?.params) {
// We only accept video adUnits
if (!bid?.mediaTypes?.[VIDEO]) return false;

// As `context`, `placement` & `plcmt` are optional (although recommended)
// values, we check the 3 of them to see if we are in an instream video context
const isInstream = bid.mediaTypes[VIDEO].context === 'instream' ||
bid.mediaTypes[VIDEO].placement === 1 ||
bid.mediaTypes[VIDEO].plcmt === 1;

// We only accept instream video context
if (!isInstream) return false;

// We need API key
return typeof bid.params.apiKey === 'string' && bid.params.apiKey.length > 10;
}

return false;
},

/**
Expand All @@ -83,15 +113,15 @@ export const spec = {
data: {
bidder_request: {
gdprConsent: {
apiVersion: bidderRequest?.gdprConsent?.apiVersion || 1,
consentString: bidderRequest?.gdprConsent?.consentString || '',
apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1),
consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''),
// Cast boolean in any case (eg: if value is int) to ensure type
gdprApplies: !!bidderRequest?.gdprConsent?.gdprApplies,
gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'),
},
refererInfo: {
page: bidderRequest?.refererInfo?.page || '',
page: deepAccess(bidderRequest, 'refererInfo.page', ''),
},
uspConsent: bidderRequest?.uspConsent || '',
uspConsent: deepAccess(bidderRequest, 'uspConsent', ''),
gppConsent: {
gppString: deepAccess(bidderRequest, 'gppConsent.gppString') ||
deepAccess(bidderRequest, 'ortb2.regs.gpp', ''),
Expand All @@ -104,15 +134,28 @@ export const spec = {
},
// Cast boolean in any case (value should be 0 or 1) to ensure type
coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'),
// In app context, we need to retrieve additional informations
...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? {
appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''),
appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''),
} : {}),
request: {
adUnitCode: bid.adUnitCode || '',
auctionId: bid.auctionId || '',
bidId: bid.bidId || '',
adUnitCode: deepAccess(bid, 'adUnitCode', ''),
auctionId: deepAccess(bid, 'auctionId', ''),
bidId: deepAccess(bid, 'bidId', ''),
mediaTypes: {
video: {
playerSize: bid.mediaTypes?.[VIDEO]?.playerSize || [],
api: bid.mediaTypes?.[VIDEO]?.api || [],
startDelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0,
mimes: bid.mediaTypes?.[VIDEO]?.mimes || [],
minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0,
maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0,
protocols: bid.mediaTypes?.[VIDEO]?.protocols || [],
skip: bid.mediaTypes?.[VIDEO]?.skip || 0,
skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0,
skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0,
startdelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0,
w: bid.mediaTypes?.[VIDEO]?.w || 0,
h: bid.mediaTypes?.[VIDEO]?.h || 0,
},
},
sizes: bid.sizes || [],
Expand Down
83 changes: 57 additions & 26 deletions modules/dailymotionBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ Maintainer: [email protected]
# Description

Dailymotion prebid adapter.
Supports video ad units in instream context.

# Configuration options

Before calling this adapter, you need to set at least the API key in the bid parameters:
Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters:

```javascript
const adUnits = [
Expand All @@ -21,8 +22,14 @@ const adUnits = [
bidder: 'dailymotion',
params: {
apiKey: 'fake_api_key'
}
}]
},
}],
code: 'test-ad-unit',
mediaTypes: {
video: {
context: 'instream',
},
},
}
];
```
Expand All @@ -39,9 +46,15 @@ const adUnits = [
bids: [{
bidder: 'dailymotion',
params: {
apiKey: 'dailymotion-testing'
}
}]
apiKey: 'dailymotion-testing',
},
}],
code: 'test-ad-unit',
mediaTypes: {
video: {
context: 'instream',
},
},
}
];
```
Expand All @@ -51,7 +64,7 @@ Please note that failing to set these will result in the adapter not bidding at
# Sample video AdUnit

To allow better targeting, you should provide as much context about the video as possible.
There are two ways of doing this depending on if you're using Dailymotion player or a third party one.
There are three ways of doing this depending on if you're using Dailymotion player or a third party one.

If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example:

Expand All @@ -61,17 +74,20 @@ const adUnits = [
bids: [{
bidder: 'dailymotion',
params: {
apiKey: 'dailymotion-testing'
apiKey: 'dailymotion-testing',
video: {
xid: 'x123456' // Dailymotion infrastructure unique video ID
},
}
}],
code: 'test-ad-unit',
mediaTypes: {
video: {
api: [2, 7],
context: 'instream',
playerSize: [ [1280, 720] ],
startDelay: 0,
xid: 'x123456' // Dailymotion infrastructure unique video ID
startdelay: 0,
w: 1280,
h: 720,
},
}
}
Expand All @@ -91,7 +107,17 @@ const adUnits = [
params: {
apiKey: 'dailymotion-testing',
video: {
description: 'overriden video description'
description: 'this is a video description',
duration: 556,
iabcat1: ['IAB-2'],
iabcat2: ['6', '17'],
id: '54321',
lang: 'FR',
livestream: 0,
private: false,
tags: 'tag_1,tag_2,tag_3',
title: 'test video',
topics: 'topic_1, topic_2',
}
}
}],
Expand All @@ -100,37 +126,42 @@ const adUnits = [
video: {
api: [2, 7],
context: 'instream',
description: 'this is a video description',
duration: 556,
iabcat2: ['6', '17'],
id: '54321',
lang: 'FR',
playerSize: [ [1280, 720] ],
private: false,
startDelay: 0,
tags: 'tag_1,tag_2,tag_3',
title: 'test video',
topics: 'topic_1, topic_2',
startdelay: 0,
w: 1280,
h: 720,
},
}
}
];
```

Each of the following video metadata fields can be added in mediaTypes.video or bids.params.video.
If a field exists in both places, it will be overridden by bids.params.video.
Each of the following video metadata fields can be added in bids.params.video.

* `description` - Video description
* `duration` - Video duration in seconds
* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv)
* `iabcat1` - List of IAB category IDs from the [1.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%201.0.tsv)
* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) and above
* `id` - Video unique ID in host video infrastructure
* `lang` - ISO 639-1 code for main language used in the video
* `livestream` - 0 = not live, 1 = content is live
* `private` - True if video is not publicly available
* `tags` - Tags for the video, comma separated
* `title` - Video title
* `topics` - Main topics for the video, comma separated
* `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player)

If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below.

| From ortb2 | Metadata fields |
|---------------------------------------------------------------------------------|-----------------|
| `ortb2.site.content.cat` OR `ortb2.site.content.data` where `ext.segtax` is `4` | `iabcat1` |
| `ortb2.site.content.data` where `ext.segtax` is `5`, `6` or `7` | `iabcat2` |
| `ortb2.site.content.id` | `id` |
| `ortb2.site.content.language` | `lang` |
| `ortb2.site.content.livestream` | `livestream` |
| `ortb2.site.content.keywords` | `tags` |
| `ortb2.site.content.title` | `title` |

# Integrating the adapter

To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **[email protected]**.
Expand Down
Loading

0 comments on commit 6be8c3b

Please sign in to comment.