github.com/prebid/prebid-server@v0.275.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/v19/native1" 9 nativeResponse "github.com/prebid/openrtb/v19/native1/response" 10 "github.com/prebid/openrtb/v19/openrtb2" 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/errortypes" 14 "github.com/prebid/prebid-server/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 } 85 86 return []*adapters.RequestData{requestData}, nil 87 } 88 89 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 90 if responseData.StatusCode == http.StatusNoContent { 91 return nil, nil 92 } 93 94 if responseData.StatusCode == http.StatusBadRequest { 95 err := &errortypes.BadInput{ 96 Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", 97 } 98 return nil, []error{err} 99 } 100 101 if responseData.StatusCode != http.StatusOK { 102 err := &errortypes.BadServerResponse{ 103 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), 104 } 105 return nil, []error{err} 106 } 107 108 var response openrtb2.BidResponse 109 if err := json.Unmarshal(responseData.Body, &response); err != nil { 110 return nil, []error{err} 111 } 112 113 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 114 bidResponse.Currency = response.Cur 115 116 var errs []error 117 for _, seatBid := range response.SeatBid { 118 for i := range seatBid.Bid { 119 bid := seatBid.Bid[i] 120 bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) 121 if err != nil { 122 errs = append(errs, err) 123 continue 124 } 125 if bidType == openrtb_ext.BidTypeNative { 126 var nativePayload nativeResponse.Response 127 if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativePayload); err != nil { 128 errs = append(errs, err) 129 continue 130 } 131 transformEventTrackers(&nativePayload) 132 nativePayloadJson, err := json.Marshal(nativePayload) 133 if err != nil { 134 errs = append(errs, err) 135 continue 136 } 137 bid.AdM = string(nativePayloadJson) 138 } 139 140 b := &adapters.TypedBid{ 141 Bid: &bid, 142 BidType: bidType, 143 } 144 bidResponse.Bids = append(bidResponse.Bids, b) 145 } 146 } 147 148 return bidResponse, errs 149 } 150 151 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 152 for _, imp := range imps { 153 if imp.ID == impID { 154 if imp.Native != nil { 155 return openrtb_ext.BidTypeNative, nil 156 } else if imp.Banner != nil { 157 return openrtb_ext.BidTypeBanner, nil 158 } else if imp.Video != nil { 159 return openrtb_ext.BidTypeVideo, nil 160 } 161 } 162 } 163 164 return "", &errortypes.BadInput{ 165 Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), 166 } 167 } 168 169 func transformEventTrackers(nativePayload *nativeResponse.Response) { 170 // the native-trk.js library used to trigger the trackers currently doesn't support the native 1.2 eventtrackers, 171 // so transform them to the deprecated imptrackers and jstracker 172 for _, eventTracker := range nativePayload.EventTrackers { 173 if eventTracker.Event != native1.EventTypeImpression { 174 continue 175 } 176 switch eventTracker.Method { 177 case native1.EventTrackingMethodImage: 178 nativePayload.ImpTrackers = append(nativePayload.ImpTrackers, eventTracker.URL) 179 case native1.EventTrackingMethodJS: 180 nativePayload.JSTracker = fmt.Sprintf("<script src=\"%s\"></script>", eventTracker.URL) 181 } 182 } 183 nativePayload.EventTrackers = nil 184 }