github.com/prebid/prebid-server/v2@v2.18.0/adapters/dmx/dmx.go (about) 1 package dmx 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/prebid/openrtb/v20/adcom1" 12 "github.com/prebid/openrtb/v20/openrtb2" 13 "github.com/prebid/prebid-server/v2/adapters" 14 "github.com/prebid/prebid-server/v2/config" 15 "github.com/prebid/prebid-server/v2/errortypes" 16 "github.com/prebid/prebid-server/v2/openrtb_ext" 17 ) 18 19 type DmxAdapter struct { 20 endpoint string 21 } 22 23 // Builder builds a new instance of the DistrictM DMX adapter for the given bidder with the given config. 24 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 25 bidder := &DmxAdapter{ 26 endpoint: config.Endpoint, 27 } 28 return bidder, nil 29 } 30 31 type dmxExt struct { 32 Bidder dmxParams `json:"bidder"` 33 } 34 35 type dmxPubExt struct { 36 Dmx dmxPubExtId `json:"dmx,omitempty"` 37 } 38 39 type dmxPubExtId struct { 40 Id string `json:"id,omitempty"` 41 } 42 43 type dmxParams struct { 44 TagId string `json:"tagid,omitempty"` 45 DmxId string `json:"dmxid,omitempty"` 46 MemberId string `json:"memberid,omitempty"` 47 PublisherId string `json:"publisher_id,omitempty"` 48 SellerId string `json:"seller_id,omitempty"` 49 Bidfloor float64 `json:"bidfloor,omitempty"` 50 } 51 52 var protocols = []adcom1.MediaCreativeSubtype{2, 3, 5, 6, 7, 8} 53 54 func UserSellerOrPubId(str1, str2 string) string { 55 if str1 != "" { 56 return str1 57 } 58 return str2 59 } 60 61 func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { 62 var imps []openrtb2.Imp 63 var rootExtInfo dmxExt 64 var publisherId string 65 var sellerId string 66 var userExt openrtb_ext.ExtUser 67 var reqCopy openrtb2.BidRequest = *request 68 var dmxReq *openrtb2.BidRequest = &reqCopy 69 var dmxRawPubId dmxPubExt 70 71 if request.User == nil { 72 if request.App == nil { 73 return nil, []error{errors.New("No user id or app id found. Could not send request to DMX.")} 74 } 75 } 76 77 if len(request.Imp) >= 1 { 78 err := json.Unmarshal(request.Imp[0].Ext, &rootExtInfo) 79 if err != nil { 80 errs = append(errs, err) 81 } else { 82 publisherId = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId) 83 sellerId = rootExtInfo.Bidder.SellerId 84 } 85 } 86 87 hasNoID := true 88 if request.App != nil { 89 appCopy := *request.App 90 appPublisherCopy := *request.App.Publisher 91 dmxReq.App = &appCopy 92 dmxReq.App.Publisher = &appPublisherCopy 93 if dmxReq.App.Publisher.ID == "" { 94 dmxReq.App.Publisher.ID = publisherId 95 } 96 dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId) 97 ext, err := json.Marshal(dmxRawPubId) 98 if err != nil { 99 errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err)) 100 return nil, errs 101 } 102 dmxReq.App.Publisher.Ext = ext 103 if dmxReq.App.ID != "" { 104 hasNoID = false 105 } 106 if hasNoID { 107 if idfa, valid := getIdfa(request); valid { 108 dmxReq.App.ID = idfa 109 hasNoID = false 110 } 111 } 112 } else { 113 dmxReq.App = nil 114 } 115 116 if request.Site != nil { 117 siteCopy := *request.Site 118 sitePublisherCopy := *request.Site.Publisher 119 dmxReq.Site = &siteCopy 120 dmxReq.Site.Publisher = &sitePublisherCopy 121 if dmxReq.Site.Publisher != nil { 122 if dmxReq.Site.Publisher.ID == "" { 123 dmxReq.Site.Publisher.ID = publisherId 124 } 125 dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId) 126 ext, err := json.Marshal(dmxRawPubId) 127 if err != nil { 128 errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err)) 129 return nil, errs 130 } 131 dmxReq.Site.Publisher.Ext = ext 132 } else { 133 dmxReq.Site.Publisher = &openrtb2.Publisher{ID: publisherId} 134 } 135 } else { 136 dmxReq.Site = nil 137 } 138 139 if request.User != nil { 140 userCopy := *request.User 141 dmxReq.User = &userCopy 142 } else { 143 dmxReq.User = nil 144 } 145 146 if dmxReq.User != nil { 147 if dmxReq.User.ID != "" { 148 hasNoID = false 149 } 150 if dmxReq.User.Ext != nil { 151 if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil { 152 if len(userExt.Eids) > 0 { 153 hasNoID = false 154 } 155 } 156 } 157 } 158 159 for _, inst := range dmxReq.Imp { 160 var ins openrtb2.Imp 161 var params dmxExt 162 const intVal int8 = 1 163 source := (*json.RawMessage)(&inst.Ext) 164 if err := json.Unmarshal(*source, ¶ms); err != nil { 165 errs = append(errs, err) 166 } 167 if isDmxParams(params.Bidder) { 168 if inst.Banner != nil { 169 if len(inst.Banner.Format) != 0 { 170 bannerCopy := *inst.Banner 171 if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { 172 imps = fetchParams(params, inst, ins, imps, &bannerCopy, nil, intVal) 173 } else { 174 return nil, []error{errors.New("Missing Params for auction to be send")} 175 } 176 } 177 } 178 179 if inst.Video != nil { 180 videoCopy := *inst.Video 181 if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" { 182 imps = fetchParams(params, inst, ins, imps, nil, &videoCopy, intVal) 183 } else { 184 return nil, []error{errors.New("Missing Params for auction to be send")} 185 } 186 } 187 } 188 189 } 190 191 dmxReq.Imp = imps 192 193 if hasNoID { 194 return nil, []error{errors.New("This request contained no identifier")} 195 } 196 197 oJson, err := json.Marshal(dmxReq) 198 199 if err != nil { 200 errs = append(errs, err) 201 return nil, errs 202 } 203 204 headers := http.Header{} 205 headers.Add("Content-Type", "application/json;charset=utf-8") 206 reqBidder := &adapters.RequestData{ 207 Method: "POST", 208 Uri: adapter.endpoint + addParams(sellerId), //adapter.endpoint, 209 Body: oJson, 210 Headers: headers, 211 ImpIDs: openrtb_ext.GetImpIDs(dmxReq.Imp), 212 } 213 214 reqsBidder = append(reqsBidder, reqBidder) 215 return 216 } 217 218 func (adapter *DmxAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 219 var errs []error 220 221 if http.StatusNoContent == response.StatusCode { 222 return nil, nil 223 } 224 225 if http.StatusBadRequest == response.StatusCode { 226 return nil, []error{&errortypes.BadInput{ 227 Message: fmt.Sprintf("Unexpected status code 400"), 228 }} 229 } 230 231 if http.StatusOK != response.StatusCode { 232 return nil, []error{&errortypes.BadInput{ 233 Message: fmt.Sprintf("Unexpected response no status code"), 234 }} 235 } 236 237 var bidResp openrtb2.BidResponse 238 239 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 240 return nil, []error{err} 241 } 242 243 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 244 245 for _, sb := range bidResp.SeatBid { 246 for i := range sb.Bid { 247 bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, request.Imp) 248 if err != nil { 249 errs = append(errs, err) 250 } else { 251 b := &adapters.TypedBid{ 252 Bid: &sb.Bid[i], 253 BidType: bidType, 254 } 255 if b.BidType == openrtb_ext.BidTypeVideo { 256 b.Bid.AdM = videoImpInsertion(b.Bid) 257 } 258 bidResponse.Bids = append(bidResponse.Bids, b) 259 } 260 } 261 } 262 return bidResponse, errs 263 264 } 265 266 func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []openrtb2.Imp, banner *openrtb2.Banner, video *openrtb2.Video, intVal int8) []openrtb2.Imp { 267 tempImp := inst 268 if params.Bidder.Bidfloor != 0 { 269 tempImp.BidFloor = params.Bidder.Bidfloor 270 } 271 if params.Bidder.TagId != "" { 272 tempImp.TagID = params.Bidder.TagId 273 tempImp.Secure = &intVal 274 } 275 276 if params.Bidder.DmxId != "" { 277 tempImp.TagID = params.Bidder.DmxId 278 tempImp.Secure = &intVal 279 } 280 if banner != nil { 281 if banner.H == nil || banner.W == nil { 282 banner.H = &banner.Format[0].H 283 banner.W = &banner.Format[0].W 284 } 285 tempImp.Banner = banner 286 } 287 288 if video != nil { 289 video.Protocols = checkProtocols(video) 290 tempImp.Video = video 291 } 292 293 if tempImp.TagID == "" { 294 return imps 295 } 296 imps = append(imps, tempImp) 297 return imps 298 } 299 300 func addParams(str string) string { 301 if str != "" { 302 return "?sellerid=" + url.QueryEscape(str) 303 } 304 return "" 305 } 306 307 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 308 mediaType := openrtb_ext.BidTypeBanner 309 for _, imp := range imps { 310 if imp.ID == impID { 311 if imp.Banner == nil && imp.Video != nil { 312 mediaType = openrtb_ext.BidTypeVideo 313 } 314 return mediaType, nil 315 } 316 } 317 318 // This shouldnt happen. Lets handle it just incase by returning an error. 319 return "", &errortypes.BadInput{ 320 Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), 321 } 322 } 323 324 func videoImpInsertion(bid *openrtb2.Bid) string { 325 adm := bid.AdM 326 nurl := bid.NURL 327 search := "</Impression>" 328 imp := "</Impression><Impression><![CDATA[%s]]></Impression>" 329 wrapped_nurl := fmt.Sprintf(imp, nurl) 330 results := strings.Replace(adm, search, wrapped_nurl, 1) 331 return results 332 } 333 334 func isDmxParams(t interface{}) bool { 335 switch t.(type) { 336 case dmxParams: 337 return true 338 default: 339 return false 340 } 341 } 342 343 func getIdfa(request *openrtb2.BidRequest) (string, bool) { 344 if request.Device == nil { 345 return "", false 346 } 347 348 device := request.Device 349 350 if device.IFA != "" { 351 return device.IFA, true 352 } 353 return "", false 354 } 355 func checkProtocols(imp *openrtb2.Video) []adcom1.MediaCreativeSubtype { 356 if len(imp.Protocols) > 0 { 357 return imp.Protocols 358 } 359 return protocols 360 }