Skip to content

Commit

Permalink
Add Zemanta adapter (#6039)
Browse files Browse the repository at this point in the history
  • Loading branch information
rokostik committed Dec 14, 2020
1 parent 30711be commit f6f27dd
Show file tree
Hide file tree
Showing 3 changed files with 850 additions and 0 deletions.
256 changes: 256 additions & 0 deletions modules/zemantaBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// jshint esversion: 6, es3: false, node: true
'use strict';

import {
registerBidder
} from '../src/adapters/bidderFactory.js';
import { NATIVE, BANNER } from '../src/mediaTypes.js';
import * as utils from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { config } from '../src/config.js';

const BIDDER_CODE = 'zemanta';
const CURRENCY = 'USD';
const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' };
const NATIVE_PARAMS = {
title: { id: 0, name: 'title' },
icon: { id: 2, type: 1, name: 'img' },
image: { id: 3, type: 3, name: 'img' },
sponsoredBy: { id: 5, name: 'data', type: 1 },
body: { id: 4, name: 'data', type: 2 },
cta: { id: 1, type: 12, name: 'data' }
};

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [ NATIVE, BANNER ],
isBidRequestValid: (bid) => {
return (
!!config.getConfig('zemanta.bidderUrl') &&
!!utils.deepAccess(bid, 'params.publisher.id') &&
!!(bid.nativeParams || bid.sizes)
);
},
buildRequests: (validBidRequests, bidderRequest) => {
const page = bidderRequest.refererInfo.referer;
const ua = navigator.userAgent;
const test = setOnAny(validBidRequests, 'params.test');
const publisher = setOnAny(validBidRequests, 'params.publisher');
const cur = CURRENCY;
const endpointUrl = config.getConfig('zemanta.bidderUrl');
const timeout = bidderRequest.timeout;

const imps = validBidRequests.map((bid, id) => {
bid.netRevenue = 'net';
const imp = {
id: id + 1 + ''
}

if (bid.params.tagid) {
imp.tagid = bid.params.tagid
}

if (bid.nativeParams) {
imp.native = {
request: JSON.stringify({
assets: getNativeAssets(bid)
})
}
} else {
imp.banner = {
format: transformSizes(bid.sizes)
}
}

return imp;
});

const request = {
id: bidderRequest.auctionId,
site: { page, publisher },
device: { ua },
source: { fd: 1 },
cur: [cur],
tmax: timeout,
imp: imps
};

if (test) {
request.is_debug = !!test;
request.test = 1;
}

if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) {
utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString)
utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1)
}
if (bidderRequest.uspConsent) {
utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent)
}
if (config.getConfig('coppa') === true) {
utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1)
}

return {
method: 'POST',
url: endpointUrl,
data: JSON.stringify(request),
bids: validBidRequests
};
},
interpretResponse: (serverResponse, { bids }) => {
if (!serverResponse.body) {
return [];
}
const { seatbid, cur } = serverResponse.body;

const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => {
result[bid.impid - 1] = bid;
return result;
}, []);

return bids.map((bid, id) => {
const bidResponse = bidResponses[id];
if (bidResponse) {
const type = bid.nativeParams ? NATIVE : BANNER;
const bidObject = {
requestId: bid.bidId,
cpm: bidResponse.price,
creativeId: bidResponse.crid,
ttl: 360,
netRevenue: bid.netRevenue === 'net',
currency: cur,
mediaType: type,
nurl: bidResponse.nurl,
};
if (type === NATIVE) {
bidObject.native = parseNative(bidResponse);
} else {
bidObject.ad = bidResponse.adm;
bidObject.width = bidResponse.w;
bidObject.height = bidResponse.h;
}
return bidObject;
}
}).filter(Boolean);
},
getUserSyncs: (syncOptions) => {
const syncs = [];
const syncUrl = config.getConfig('zemanta.usersyncUrl');
if (syncOptions.pixelEnabled && syncUrl) {
syncs.push({
type: 'image',
url: syncUrl
});
}
return syncs;
},
onBidWon: (bid) => {
ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm))
}
};

registerBidder(spec);

function parseNative(bid) {
const { assets, link, eventtrackers } = JSON.parse(bid.adm);
const result = {
clickUrl: link.url,
clickTrackers: link.clicktrackers || undefined
};
assets.forEach(asset => {
const kind = NATIVE_ASSET_IDS[asset.id];
const content = kind && asset[NATIVE_PARAMS[kind].name];
if (content) {
result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h };
}
});
if (eventtrackers) {
result.impressionTrackers = [];
eventtrackers.forEach(tracker => {
if (tracker.event !== 1) return;
switch (tracker.method) {
case 1: // img
result.impressionTrackers.push(tracker.url);
break;
case 2: // js
result.javascriptTrackers = `<script src=\"${tracker.url}\"></script>`;
break;
}
});
}
return result;
}

function setOnAny(collection, key) {
for (let i = 0, result; i < collection.length; i++) {
result = utils.deepAccess(collection[i], key);
if (result) {
return result;
}
}
}

function flatten(arr) {
return [].concat(...arr);
}

function getNativeAssets(bid) {
return utils._map(bid.nativeParams, (bidParams, key) => {
const props = NATIVE_PARAMS[key];
const asset = {
required: bidParams.required & 1,
};
if (props) {
asset.id = props.id;
let wmin, hmin, w, h;
let aRatios = bidParams.aspect_ratios;

if (aRatios && aRatios[0]) {
aRatios = aRatios[0];
wmin = aRatios.min_width || 0;
hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0;
}

if (bidParams.sizes) {
const sizes = flatten(bidParams.sizes);
w = sizes[0];
h = sizes[1];
}

asset[props.name] = {
len: bidParams.len,
type: props.type,
wmin,
hmin,
w,
h
};

return asset;
}
}).filter(Boolean);
}

/* Turn bid request sizes into ut-compatible format */
function transformSizes(requestSizes) {
if (!utils.isArray(requestSizes)) {
return [];
}

if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) {
return [{
w: parseInt(requestSizes[0], 10),
h: parseInt(requestSizes[1], 10)
}];
} else if (utils.isArray(requestSizes[0])) {
return requestSizes.map(item =>
({
w: parseInt(item[0], 10),
h: parseInt(item[1], 10)
})
);
}

return [];
}
107 changes: 107 additions & 0 deletions modules/zemantaBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Overview

```
Module Name: Zemanta Adapter
Module Type: Bidder Adapter
Maintainer: prog-ops-team@outbrain.com
```

# Description

Module that connects to zemanta bidder to fetch bids.
Both native and display formats are supported but not at the same time. Using OpenRTB standard.

# Configuration

## Bidder and usersync URLs

The Zemanta adapter does not work without setting the correct bidder and usersync URLs.
You will receive the URLs when contacting us.

```
pbjs.setConfig({
zemanta: {
bidderUrl: 'https://bidder-url.com',
usersyncUrl: 'https://usersync-url.com'
}
});
```


# Test Native Parameters
```
var adUnits = [
code: '/19968336/prebid_native_example_1',
mediaTypes: {
native: {
image: {
required: false,
sizes: [100, 50]
},
title: {
required: false,
len: 140
},
sponsoredBy: {
required: false
},
clickUrl: {
required: false
},
body: {
required: false
},
icon: {
required: false,
sizes: [50, 50]
}
}
},
bids: [{
bidder: 'zemanta',
params: {
publisher: {
id: '2706', // required
name: 'Publishers Name',
domain: 'publisher.com'
},
tagid: 'tag-id'
}
}]
];
pbjs.setConfig({
zemanta: {
bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/'
}
});
```

# Test Display Parameters
```
var adUnits = [
code: '/19968336/prebid_display_example_1',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
bids: [{
bidder: 'zemanta',
params: {
publisher: {
id: '2706', // required
name: 'Publishers Name',
domain: 'publisher.com'
},
tagid: 'tag-id'
},
}]
];
pbjs.setConfig({
zemanta: {
bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/'
}
});
```
Loading

0 comments on commit f6f27dd

Please sign in to comment.