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