github.com/prebid/prebid-server/v2@v2.18.0/adapters/outbrain/outbrain.go (about) 1 package outbrain 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/prebid/openrtb/v20/native1" 9 nativeResponse "github.com/prebid/openrtb/v20/native1/response" 10 "github.com/prebid/openrtb/v20/openrtb2" 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/errortypes" 14 "github.com/prebid/prebid-server/v2/openrtb_ext" 15 ) 16 17 type adapter struct { 18 endpoint string 19 } 20 21 // Builder builds a new instance of the Outbrain adapter for the given bidder with the given config. 22 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 23 bidder := &adapter{ 24 endpoint: config.Endpoint, 25 } 26 return bidder, nil 27 } 28 29 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 30 reqCopy := *request 31 32 var errs []error 33 var outbrainExt openrtb_ext.ExtImpOutbrain 34 for i := 0; i < len(reqCopy.Imp); i++ { 35 imp := reqCopy.Imp[i] 36 37 var bidderExt adapters.ExtImpBidder 38 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 39 errs = append(errs, err) 40 continue 41 } 42 if err := json.Unmarshal(bidderExt.Bidder, &outbrainExt); err != nil { 43 errs = append(errs, err) 44 continue 45 } 46 if outbrainExt.TagId != "" { 47 imp.TagID = outbrainExt.TagId 48 reqCopy.Imp[i] = imp 49 } 50 } 51 52 publisher := &openrtb2.Publisher{ 53 ID: outbrainExt.Publisher.Id, 54 Name: outbrainExt.Publisher.Name, 55 Domain: outbrainExt.Publisher.Domain, 56 } 57 if reqCopy.Site != nil { 58 siteCopy := *reqCopy.Site 59 siteCopy.Publisher = publisher 60 reqCopy.Site = &siteCopy 61 } else if reqCopy.App != nil { 62 appCopy := *reqCopy.App 63 appCopy.Publisher = publisher 64 reqCopy.App = &appCopy 65 } 66 67 if outbrainExt.BCat != nil { 68 reqCopy.BCat = outbrainExt.BCat 69 } 70 if outbrainExt.BAdv != nil { 71 reqCopy.BAdv = outbrainExt.BAdv 72 } 73 74 requestJSON, err := json.Marshal(reqCopy) 75 if err != nil { 76 errs = append(errs, err) 77 return nil, errs 78 } 79 80 requestData := &adapters.RequestData{ 81 Method: "POST", 82 Uri: a.endpoint, 83 Body: requestJSON, 84 ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), 85 } 86 87 return []*adapters.RequestData{requestData}, nil 88 } 89 90 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 91 if responseData.StatusCode == http.StatusNoContent { 92 return nil, nil 93 } 94 95 if responseData.StatusCode == http.StatusBadRequest { 96 err := &errortypes.BadInput{ 97 Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", 98 } 99 return nil, []error{err} 100 } 101 102 if responseData.StatusCode != http.StatusOK { 103 err := &errortypes.BadServerResponse{ 104 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), 105 } 106 return nil, []error{err} 107 } 108 109 var response openrtb2.BidResponse 110 if err := json.Unmarshal(responseData.Body, &response); err != nil { 111 return nil, []error{err} 112 } 113 114 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 115 bidResponse.Currency = response.Cur 116 117 var errs []error 118 for _, seatBid := range response.SeatBid { 119 for i := range seatBid.Bid { 120 bid := seatBid.Bid[i] 121 bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) 122 if err != nil { 123 errs = append(errs, err) 124 continue 125 } 126 if bidType == openrtb_ext.BidTypeNative { 127 var nativePayload nativeResponse.Response 128 if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativePayload); err != nil { 129 errs = append(errs, err) 130 continue 131 } 132 transformEventTrackers(&nativePayload) 133 nativePayloadJson, err := json.Marshal(nativePayload) 134 if err != nil { 135 errs = append(errs, err) 136 continue 137 } 138 bid.AdM = string(nativePayloadJson) 139 } 140 141 b := &adapters.TypedBid{ 142 Bid: &bid, 143 BidType: bidType, 144 } 145 bidResponse.Bids = append(bidResponse.Bids, b) 146 } 147 } 148 149 return bidResponse, errs 150 } 151 152 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 153 for _, imp := range imps { 154 if imp.ID == impID { 155 if imp.Native != nil { 156 return openrtb_ext.BidTypeNative, nil 157 } else if imp.Banner != nil { 158 return openrtb_ext.BidTypeBanner, nil 159 } else if imp.Video != nil { 160 return openrtb_ext.BidTypeVideo, nil 161 } 162 } 163 } 164 165 return "", &errortypes.BadInput{ 166 Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), 167 } 168 } 169 170 func transformEventTrackers(nativePayload *nativeResponse.Response) { 171 // the native-trk.js library used to trigger the trackers currently doesn't support the native 1.2 eventtrackers, 172 // so transform them to the deprecated imptrackers and jstracker 173 for _, eventTracker := range nativePayload.EventTrackers { 174 if eventTracker.Event != native1.EventTypeImpression { 175 continue 176 } 177 switch eventTracker.Method { 178 case native1.EventTrackingMethodImage: 179 nativePayload.ImpTrackers = append(nativePayload.ImpTrackers, eventTracker.URL) 180 case native1.EventTrackingMethodJS: 181 nativePayload.JSTracker = fmt.Sprintf("<script src=\"%s\"></script>", eventTracker.URL) 182 } 183 } 184 nativePayload.EventTrackers = nil 185 }