diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index b934ac753a0..16aca28f266 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -3,19 +3,18 @@ package improvedigital import ( "encoding/json" "fmt" - "net/http" - "strconv" - "strings" - "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "regexp" + "strconv" + "strings" ) const ( - buyingTypeRTB = "rtb" isRewardedInventory = "is_rewarded_inventory" stateRewardedInventoryEnable = "1" consentProvidersSettingsInputKey = "ConsentedProvidersSettings" @@ -43,6 +42,8 @@ type ImpExtBidder struct { } } +var dealDetectionRegEx, _ = regexp.Compile("(classic|deal)") + // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) @@ -134,7 +135,6 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } seatBid := bidResp.SeatBid[0] - if len(seatBid.Bid) == 0 { return nil, nil } @@ -145,7 +145,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e for i := range seatBid.Bid { bid := seatBid.Bid[i] - bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) + bidType, err := getBidType(bid, internalRequest.Imp) if err != nil { return nil, []error{err} } @@ -158,7 +158,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } bidExtImprovedigital := bidExt.Improvedigital - if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB { + if bidExtImprovedigital.LineItemID != 0 && dealDetectionRegEx.MatchString(bidExtImprovedigital.BuyingType) { bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID) } } @@ -169,7 +169,6 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e }) } return bidResponse, nil - } // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config. @@ -180,33 +179,84 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } - - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } +func getBidType(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + // there must be a matching imp against bid.ImpID + imp, err := findImpByID(bid.ImpID, imps) + if err != nil { + return "", err + } - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil + // if MType is not set in server response, try to determine it + if bid.MType == 0 { + if !isMultiFormatImp(imp) { + // Not a bid for multi format impression. So, determine MType from impression + if imp.Banner != nil { + bid.MType = openrtb2.MarkupBanner + } else if imp.Video != nil { + bid.MType = openrtb2.MarkupVideo + } else if imp.Audio != nil { + bid.MType = openrtb2.MarkupAudio + } else if imp.Native != nil { + bid.MType = openrtb2.MarkupNative + } else { // This should not happen. + // Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Could not determine MType from impression with ID: \"%s\"", bid.ImpID), + } } - + } else { return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + Message: fmt.Sprintf("Bid must have non-zero MType for multi format impression with ID: \"%s\"", bid.ImpID), } } } - // This shouldnt happen. Lets handle it just incase by returning an error. - return "", &errortypes.BadServerResponse{ + // map MType to BidType + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + // This shouldn't happen. Let's handle it just in case by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d for impression with ID: \"%s\"", bid.MType, bid.ImpID), + } + } +} + +func findImpByID(impID string, imps []openrtb2.Imp) (openrtb2.Imp, error) { + for _, imp := range imps { + if imp.ID == impID { + return imp, nil + } + } + return openrtb2.Imp{}, &errortypes.BadServerResponse{ Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), } } +func isMultiFormatImp(imp openrtb2.Imp) bool { + formatCount := 0 + if imp.Banner != nil { + formatCount++ + } + if imp.Video != nil { + formatCount++ + } + if imp.Audio != nil { + formatCount++ + } + if imp.Native != nil { + formatCount++ + } + return formatCount > 1 +} + // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request openrtb2.BidRequest) ([]byte, error) { var cpStr string diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json index 6963e82f9c4..425435049db 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json @@ -40,6 +40,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } } ] }, @@ -159,6 +181,71 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "appID", + "publisher": { + "id": "uniq_pub_id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], "expectedBidResponses": [ @@ -197,6 +284,24 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid2", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/native.json b/adapters/improvedigital/improvedigitaltest/exemplary/native.json index 3309b35a753..3865d40383b 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/native.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/native.json @@ -17,7 +17,11 @@ "imp": [ { "id": "native", - "ext": { "improvedigital": { "placementId": 1234 } }, + "ext": { + "bidder": { + "placementId": 1234 + } + }, "native": { "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" @@ -57,7 +61,11 @@ "request": "{\"context\":1,\"plcmttype\":1,\"eventtrackers\":[{\"event\":1,\"methods\":[1]}],\"assets\":[{\"required\":1,\"title\":{\"len\":80}},{\"required\":1,\"data\":{\"type\":2}},{\"required\":0,\"data\":{\"type\":12}},{\"required\":0,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":225,\"ext\":{\"aspectratios\":[\"4:3\"]}}},{\"required\":0,\"img\":{\"type\":1,\"w\":128,\"h\":128}},{\"required\":0,\"data\":{\"type\":3}},{\"required\":0,\"data\":{\"type\":6}}]}", "ver": "1.2" }, - "ext": { "improvedigital": { "placementId": 1234 } } + "ext": { + "bidder": { + "placementId": 1234 + } + } } ], "site": { diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json index 0945b4c0f69..d7131a54577 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json @@ -42,6 +42,28 @@ "placementId": 13244 } } + }, + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } } ] }, @@ -166,6 +188,75 @@ "cur": "USD" } } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "domain": "good.site", + "publisher": { + "id": "uniq_pub_id" + }, + "keywords": "omgword", + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-multi-format-id-with-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } } ], @@ -205,6 +296,26 @@ "type": "video" } ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid1", + "impid": "test-multi-format-id-with-mtype", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + } + ] } ] } diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json new file mode 100644 index 00000000000..f62e5f2379c --- /dev/null +++ b/adapters/improvedigital/improvedigitaltest/supplemental/missing_and_unsupported_mtype.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + }, + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "improvedigital", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 5 + }] + }], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-multi-format-id-without-mtype", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-multi-format-id-without-mtype", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unsupported MType 5 for impression with ID: \"test-imp-id\"", + "comparison": "literal" + }, + { + "value": "Bid must have non-zero MType for multi format impression with ID: \"test-multi-format-id-without-mtype\"", + "comparison": "literal" + } + ] +} diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index d14356cde0e..4cd30d7c6b2 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -7,14 +7,16 @@ capabilities: mediaTypes: - banner - video + - audio - native site: mediaTypes: - banner - video + - audio - native userSync: redirect: - url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "{PUB_USER_ID}" endpointCompression: "GZIP" \ No newline at end of file