github.com/prebid/prebid-server@v0.275.0/adapters/pubnative/pubnative.go (about) 1 package pubnative 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 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 PubnativeAdapter struct { 18 URI string 19 } 20 21 func (a *PubnativeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 22 impCount := len(request.Imp) 23 requestData := make([]*adapters.RequestData, 0, impCount) 24 errs := []error{} 25 26 headers := http.Header{} 27 headers.Add("Content-Type", "application/json;charset=utf-8") 28 headers.Add("Accept", "application/json") 29 30 err := checkRequest(request) 31 if err != nil { 32 errs = append(errs, err) 33 return nil, errs 34 } 35 36 for _, imp := range request.Imp { 37 requestCopy := *request 38 var bidderExt adapters.ExtImpBidder 39 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 40 errs = append(errs, err) 41 continue 42 } 43 44 var pubnativeExt openrtb_ext.ExtImpPubnative 45 if err := json.Unmarshal(bidderExt.Bidder, &pubnativeExt); err != nil { 46 errs = append(errs, err) 47 continue 48 } 49 50 err := convertImpression(&imp) 51 if err != nil { 52 errs = append(errs, err) 53 continue 54 } 55 56 requestCopy.Imp = []openrtb2.Imp{imp} 57 reqJSON, err := json.Marshal(&requestCopy) 58 if err != nil { 59 errs = append(errs, err) 60 return nil, errs 61 } 62 63 queryParams := url.Values{} 64 queryParams.Add("apptoken", pubnativeExt.AppAuthToken) 65 queryParams.Add("zoneid", strconv.Itoa(pubnativeExt.ZoneID)) 66 queryString := queryParams.Encode() 67 68 reqData := &adapters.RequestData{ 69 Method: "POST", 70 Uri: fmt.Sprintf("%s?%s", a.URI, queryString), 71 Body: reqJSON, 72 Headers: headers, 73 } 74 75 requestData = append(requestData, reqData) 76 } 77 return requestData, errs 78 } 79 80 func checkRequest(request *openrtb2.BidRequest) error { 81 if request.Device == nil || len(request.Device.OS) == 0 { 82 return &errortypes.BadInput{ 83 Message: "Impression is missing device OS information", 84 } 85 } 86 87 return nil 88 } 89 90 func convertImpression(imp *openrtb2.Imp) error { 91 if imp.Banner == nil && imp.Video == nil && imp.Native == nil { 92 return &errortypes.BadInput{ 93 Message: "Pubnative only supports banner, video or native ads.", 94 } 95 } 96 if imp.Banner != nil { 97 bannerCopy, err := convertBanner(imp.Banner) 98 if err != nil { 99 return err 100 } 101 imp.Banner = bannerCopy 102 } 103 104 return nil 105 } 106 107 // make sure that banner has openrtb 2.3-compatible size information 108 func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { 109 if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { 110 if len(banner.Format) > 0 { 111 f := banner.Format[0] 112 113 bannerCopy := *banner 114 115 bannerCopy.W = openrtb2.Int64Ptr(f.W) 116 bannerCopy.H = openrtb2.Int64Ptr(f.H) 117 118 return &bannerCopy, nil 119 } else { 120 return nil, &errortypes.BadInput{ 121 Message: "Size information missing for banner", 122 } 123 } 124 } 125 return banner, nil 126 } 127 128 func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 129 if response.StatusCode == http.StatusNoContent { 130 return nil, nil 131 } 132 133 if response.StatusCode == http.StatusBadRequest { 134 return nil, []error{&errortypes.BadInput{ 135 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 136 }} 137 } 138 139 if response.StatusCode != http.StatusOK { 140 return nil, []error{&errortypes.BadServerResponse{ 141 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 142 }} 143 } 144 145 var parsedResponse openrtb2.BidResponse 146 if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { 147 return nil, []error{&errortypes.BadServerResponse{ 148 Message: err.Error(), 149 }} 150 } 151 152 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 153 154 for _, sb := range parsedResponse.SeatBid { 155 for i := 0; i < len(sb.Bid); i++ { 156 bid := sb.Bid[i] 157 if bid.Price != 0 { 158 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 159 Bid: &bid, 160 BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), 161 }) 162 } 163 } 164 } 165 return bidResponse, nil 166 } 167 168 // Builder builds a new instance of the Pubnative adapter for the given bidder with the given config. 169 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 170 bidder := &PubnativeAdapter{ 171 URI: config.Endpoint, 172 } 173 return bidder, nil 174 } 175 176 func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { 177 mediaType := openrtb_ext.BidTypeBanner 178 for _, imp := range imps { 179 if imp.ID == impId { 180 if imp.Video != nil { 181 mediaType = openrtb_ext.BidTypeVideo 182 } else if imp.Native != nil { 183 mediaType = openrtb_ext.BidTypeNative 184 } 185 return mediaType 186 } 187 } 188 return mediaType 189 }