diff --git a/modules/michaoBidAdapter.js b/modules/michaoBidAdapter.js
new file mode 100644
index 00000000000..953f0e05268
--- /dev/null
+++ b/modules/michaoBidAdapter.js
@@ -0,0 +1,240 @@
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { Renderer } from '../src/Renderer.js';
+import {
+ deepSetValue,
+ isNumber,
+ isStr,
+ logError,
+ replaceAuctionPrice,
+ triggerPixel,
+} from '../src/utils.js';
+
+const ENV = {
+ BIDDER_CODE: 'michao',
+ SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO, NATIVE],
+ ENDPOINT: 'https://rtb.michao-ssp.com/openrtb/prebid',
+ NET_REVENUE: true,
+ DEFAULT_CURRENCY: 'USD',
+ RENDERER_URL:
+ 'https://cdn.jsdelivr.net/npm/in-renderer-js@latest/dist/in-video-renderer.umd.min.js',
+};
+
+export const spec = {
+ code: ENV.BIDDER_CODE,
+ supportedMediaTypes: ENV.SUPPORTED_MEDIA_TYPES,
+
+ isBidRequestValid: function (bid) {
+ if (!hasParamsObject(bid)) {
+ return false;
+ }
+
+ if (!validateMichaoParams(bid.params)) {
+ domainLogger.bidRequestValidationError();
+ return false;
+ }
+
+ return true;
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ const bidRequests = [];
+
+ validBidRequests.forEach((validBidRequest) => {
+ if (hasVideoMediaType(validBidRequest)) {
+ bidRequests.push(buildRequest(validBidRequest, bidderRequest, 'video'));
+ }
+
+ if (hasBannerMediaType(validBidRequest)) {
+ bidRequests.push(
+ buildRequest(validBidRequest, bidderRequest, 'banner')
+ );
+ }
+
+ if (hasNativeMediaType(validBidRequest)) {
+ bidRequests.push(
+ buildRequest(validBidRequest, bidderRequest, 'native')
+ );
+ }
+ });
+
+ return bidRequests;
+ },
+
+ interpretResponse: function (serverResponse, request) {
+ return interpretResponse(serverResponse, request);
+ },
+
+ getUserSyncs: function (
+ syncOptions,
+ serverResponses,
+ gdprConsent,
+ uspConsent
+ ) {
+ if (syncOptions.iframeEnabled) {
+ return [syncUser(gdprConsent)];
+ }
+ },
+
+ onBidBillable: function (bid) {
+ if (bid.burl && isStr(bid.burl)) {
+ billBid(bid);
+ }
+ },
+};
+
+export const domainLogger = {
+ bidRequestValidationError() {
+ logError('Michao Bid Adapter: wrong format of site or placement.');
+ },
+};
+
+export function buildRequest(bidRequest, bidderRequest, mediaType) {
+ const openRTBBidRequest = converter.toORTB({
+ bidRequests: [bidRequest],
+ bidderRequest,
+ context: {
+ mediaType: mediaType,
+ },
+ });
+
+ return {
+ method: 'POST',
+ url: ENV.ENDPOINT,
+ data: openRTBBidRequest,
+ options: { contentType: 'application/json', withCredentials: true },
+ };
+}
+
+export function interpretResponse(response, request) {
+ const bids = converter.fromORTB({
+ response: response.body,
+ request: request.data,
+ }).bids;
+
+ return bids;
+}
+
+export function syncUser(gdprConsent) {
+ let gdprParams = '';
+
+ if (typeof gdprConsent === 'object') {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${
+ gdprConsent.consentString
+ }`;
+ } else {
+ gdprParams = `gdpr_consent=${gdprConsent.consentString}`;
+ }
+ }
+
+ return {
+ type: 'iframe',
+ url: 'https://sync.michao-ssp.com/cookie-syncs?' + gdprParams,
+ };
+}
+
+export function addRenderer(bid) {
+ bid.renderer.push(() => {
+ const inRenderer = new window.InVideoRenderer();
+ inRenderer.render(bid.adUnitCode, bid);
+ });
+}
+
+export function hasParamsObject(bid) {
+ return typeof bid.params === 'object';
+}
+
+export function validateMichaoParams(params) {
+ let valid = true;
+
+ if (!isNumber(params?.site)) {
+ valid = false;
+ }
+
+ if (!isStr(params?.placement)) {
+ valid = false;
+ }
+
+ return valid
+}
+
+export function billBid(bid) {
+ bid.burl = replaceAuctionPrice(bid.burl, bid.originalCpm || bid.cpm);
+ triggerPixel(bid.burl);
+}
+
+const converter = ortbConverter({
+ request(buildRequest, imps, bidderRequest, context) {
+ const bidRequest = context.bidRequests[0];
+ const openRTBBidRequest = buildRequest(imps, bidderRequest, context);
+ openRTBBidRequest.cur = [ENV.DEFAULT_CURRENCY];
+ openRTBBidRequest.test = config.getConfig('debug') ? 1 : 0;
+ openRTBBidRequest.bcat = bidRequest.params?.bcat || [];
+ openRTBBidRequest.badv = bidRequest.params?.badv || [];
+ deepSetValue(
+ openRTBBidRequest,
+ 'site.id',
+ bidRequest.params.site.toString()
+ );
+ if (bidRequest?.schain) {
+ deepSetValue(openRTBBidRequest, 'source.schain', bidRequest.schain);
+ }
+
+ return openRTBBidRequest;
+ },
+
+ imp(buildImp, bidRequest, context) {
+ const imp = buildImp(bidRequest, context);
+ deepSetValue(imp, 'ext.placement', bidRequest.params.placement.toString());
+ deepSetValue(imp, 'rwdd', bidRequest.params?.reward ? 1 : 0);
+ deepSetValue(imp, 'bidfloor', isNumber(bidRequest.params?.bidFloor) ? bidRequest.params?.bidFloor : 0);
+
+ return imp;
+ },
+
+ bidResponse(buildBidResponse, bid, context) {
+ const { bidRequest } = context;
+ let bidResponse = buildBidResponse(bid, context);
+ if (hasVideoMediaType(bidRequest)) {
+ bidResponse.vastXml = bid.adm;
+ if (bidRequest.mediaTypes.video?.context === 'outstream') {
+ const renderer = Renderer.install({
+ id: bid.bidId,
+ url: ENV.RENDERER_URL,
+ adUnitCode: bid.adUnitCode,
+ });
+ renderer.setRender(addRenderer);
+ bidResponse.renderer = renderer;
+ }
+ }
+
+ return bidResponse;
+ },
+
+ context: {
+ netRevenue: ENV.NET_REVENUE,
+ currency: ENV.DEFAULT_CURRENCY,
+ ttl: 360,
+ },
+});
+
+function hasBannerMediaType(bid) {
+ return hasMediaType(bid, 'banner');
+}
+
+function hasVideoMediaType(bid) {
+ return hasMediaType(bid, 'video');
+}
+
+function hasNativeMediaType(bid) {
+ return hasMediaType(bid, 'native');
+}
+
+function hasMediaType(bid, mediaType) {
+ return bid.mediaTypes.hasOwnProperty(mediaType);
+}
+
+registerBidder(spec);
diff --git a/modules/michaoBidAdapter.md b/modules/michaoBidAdapter.md
new file mode 100644
index 00000000000..b45e8e2b5bd
--- /dev/null
+++ b/modules/michaoBidAdapter.md
@@ -0,0 +1,87 @@
+# Overview
+
+```markdown
+Module Name: Michao Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: miyamoto.kai@lookverin.com
+```
+
+# Description
+
+Module that connects to Michao’s demand sources
+
+Supported Ad format:
+* Banner
+* Video (instream and outstream)
+* Native
+
+# Test Parameters
+```
+var adUnits = [
+ // Banner adUnit
+ {
+ code: 'banner-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]],
+ }
+ },
+ bids: [{
+ bidder: 'michao',
+ params: {
+ site: 1,
+ placement: '1',
+ }
+ }]
+ },
+ // Video adUnit
+ {
+ code: 'video-div',
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ minduration: 0,
+ maxduration: 120,
+ mimes: ['video/mp4'],
+ protocols: [7]
+ }
+ },
+ bids: [{
+ bidder: 'michao',
+ params: {
+ site: 1,
+ placement: '1',
+ }
+ }]
+ },
+ // Native AdUnit
+ {
+ code: 'native-div',
+ mediaTypes: {
+ native: {
+ ortb: {
+ assets: [
+ {
+ id: 1,
+ required: 1,
+ img: {
+ type: 3,
+ w: 989,
+ h: 742,
+ },
+ },
+ ]
+ }
+ }
+ },
+ bids: [{
+ bidder: 'michao',
+ params: {
+ site: 1,
+ placement: '1',
+ }
+ }]
+ }
+];
+```
diff --git a/test/spec/modules/michaoBidAdapter_spec.js b/test/spec/modules/michaoBidAdapter_spec.js
new file mode 100644
index 00000000000..47dea68b538
--- /dev/null
+++ b/test/spec/modules/michaoBidAdapter_spec.js
@@ -0,0 +1,812 @@
+import { expect } from 'chai';
+import {
+ addRenderer,
+ billBid,
+ buildRequest,
+ domainLogger,
+ interpretResponse,
+ spec,
+ syncUser,
+ validateMichaoParams,
+} from '../../../modules/michaoBidAdapter';
+import * as utils from 'src/utils.js';
+import { config } from '../../../src/config';
+
+describe('the michao bidder adapter', () => {
+ beforeEach(() => {
+ config.resetConfig();
+ });
+
+ describe('unit', () => {
+ describe('validate bid request', () => {
+ const invalidBidParams = [
+ { site: '123', placement: 'super-placement' },
+ { site: '123', placement: 456 },
+ { site: Infinity, placement: 456 },
+ ];
+ invalidBidParams.forEach((params) => {
+ it('Detecting incorrect parameters', () => {
+ const result = validateMichaoParams(params);
+
+ expect(result).to.be.false;
+ });
+ });
+
+ it('If the site ID and placement ID are correct, the verification succeeds.', () => {
+ const params = {
+ site: 123,
+ placement: 'placement'
+ };
+
+ const result = validateMichaoParams(params);
+
+ expect(result).to.be.true;
+ });
+ });
+
+ describe('build bid request', () => {
+ it('Banner bid requests are converted to banner server request objects', () => {
+ const bannerBidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bannerBidRequest],
+ };
+
+ const result = buildRequest(bannerBidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ url: 'https://rtb.michao-ssp.com/openrtb/prebid',
+ 'options.contentType': 'application/json',
+ 'options.withCredentials': true,
+ method: 'POST',
+ 'data.cur[0]': 'USD',
+ 'data.imp[0].ext.placement': '456',
+ 'data.site.id': '123',
+ 'data.test': 0,
+ });
+ });
+
+ it('Video bid requests are converted to video server request objects', () => {
+ const videoBidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 480],
+ mimes: ['video/mp4'],
+ },
+ },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [videoBidRequest],
+ };
+
+ const result = buildRequest(videoBidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ url: 'https://rtb.michao-ssp.com/openrtb/prebid',
+ 'options.contentType': 'application/json',
+ 'options.withCredentials': true,
+ method: 'POST',
+ 'data.cur[0]': 'USD',
+ 'data.imp[0].ext.placement': '456',
+ 'data.site.id': '123',
+ 'data.test': 0,
+ });
+ });
+
+ it('Native bid requests are converted to video server request objects', () => {
+ const nativeBidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: {
+ native: {
+ ortb: {
+ assets: [
+ {
+ id: 2,
+ required: 1,
+ title: {
+ len: 80
+ }
+ }
+ ]
+ }
+ }
+ },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [nativeBidRequest],
+ };
+
+ const result = buildRequest(nativeBidRequest, bidderRequest, 'native');
+
+ expect(result).to.nested.include({
+ url: 'https://rtb.michao-ssp.com/openrtb/prebid',
+ 'options.contentType': 'application/json',
+ 'options.withCredentials': true,
+ method: 'POST',
+ 'data.cur[0]': 'USD',
+ 'data.imp[0].ext.placement': '456',
+ 'data.site.id': '123',
+ 'data.test': 0,
+ });
+ });
+
+ it('Converted to server request object for testing in debug mode', () => {
+ const bidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bidRequest],
+ };
+ config.setConfig({
+ debug: true,
+ });
+
+ const result = buildRequest(bidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ 'data.test': 1,
+ });
+ });
+
+ it('Specifying a reward builds a bid request for the reward', () => {
+ const bidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ reward: true,
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bidRequest],
+ };
+ config.setConfig({
+ debug: true,
+ });
+
+ const result = buildRequest(bidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ 'data.imp[0].rwdd': 1,
+ });
+ });
+
+ it('Block categories are set in the bid request through parameters', () => {
+ const bidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ bcat: ['IAB2']
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bidRequest],
+ };
+ config.setConfig({
+ debug: true,
+ });
+
+ const result = buildRequest(bidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ 'data.bcat[0]': 'IAB2',
+ });
+ });
+
+ it('Block advertisers set in bid request through parameters', () => {
+ const bidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ badv: ['adomain.com']
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bidRequest],
+ };
+ config.setConfig({
+ debug: true,
+ });
+
+ const result = buildRequest(bidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ 'data.badv[0]': 'adomain.com',
+ });
+ });
+
+ it('The lowest bid will be set', () => {
+ const bidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ bidFloor: 1,
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bidRequest],
+ };
+ config.setConfig({
+ debug: true,
+ });
+
+ const result = buildRequest(bidRequest, bidderRequest, 'banner');
+
+ expect(result).to.nested.include({
+ 'data.imp[0].bidfloor': 1,
+ });
+ });
+ });
+
+ describe('interpret response', () => {
+ it('Server response is interpreted as a bid.', () => {
+ const response = {
+ headers: null,
+ body: {
+ id: 'requestId',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: 'bidId1',
+ impid: 'bidId1',
+ price: 0.18,
+ adm: '',
+ adid: '144762342',
+ adomain: ['https://dummydomain.com'],
+ iurl: 'iurl',
+ cid: '109',
+ crid: 'creativeId',
+ cat: [],
+ w: 300,
+ h: 250,
+ mtype: 1,
+ },
+ ],
+ seat: 'seat',
+ },
+ ],
+ cur: 'USD',
+ },
+ };
+ const bannerBidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: 'bidId1',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bannerBidRequest],
+ };
+ const request = buildRequest(bannerBidRequest, bidderRequest, 'banner');
+
+ const result = interpretResponse(response, request);
+
+ expect(result).to.be.an('array');
+ expect(result[0]).to.have.property('currency', 'USD');
+ expect(result[0]).to.have.property('requestId', 'bidId1');
+ expect(result[0]).to.have.property('cpm', 0.18);
+ expect(result[0]).to.have.property('width', 300);
+ expect(result[0]).to.have.property('height', 250);
+ expect(result[0]).to.have.property('ad', '');
+ expect(result[0]).to.have.property('creativeId', 'creativeId');
+ expect(result[0]).to.have.property('netRevenue', true);
+ });
+
+ it('Empty server responses are interpreted as empty bids', () => {
+ const response = { body: {} };
+ const bannerBidRequest = {
+ adUnitCode: 'test-div',
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ bidId: '22c4871113f461',
+ bidder: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bidRequestsCount: 1,
+ bidderRequestsCount: 1,
+ bidderWinsCount: 0,
+ mediaTypes: { banner: [[300, 250]] },
+ params: {
+ site: 123,
+ placement: '456',
+ },
+ };
+ const bidderRequest = {
+ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+ auctionStart: 1579746300522,
+ bidderCode: 'michao',
+ bidderRequestId: '15246a574e859f',
+ bids: [bannerBidRequest],
+ };
+ const request = buildRequest(bannerBidRequest, bidderRequest, 'banner');
+
+ const result = interpretResponse(response, request);
+
+ expect(result).to.be.an('array');
+ expect(result.length).to.equal(0);
+ });
+
+ it('Set renderer with outstream video ads', () => {
+ const response = {
+ headers: null,
+ body: {
+ id: 'requestId',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: 'bidId1',
+ impid: 'bidId1',
+ price: 0.18,
+ adm: '