github.com/prebid/prebid-server@v0.275.0/adapters/silverpush/silverpush.go (about) 1 package silverpush 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/prebid/openrtb/v19/openrtb2" 9 "github.com/prebid/prebid-server/adapters" 10 "github.com/prebid/prebid-server/config" 11 "github.com/prebid/prebid-server/errortypes" 12 "github.com/prebid/prebid-server/openrtb_ext" 13 ) 14 15 const ( 16 bidderConfig = "sp_pb_ortb" 17 bidderVersion = "1.0.0" 18 ) 19 20 type adapter struct { 21 bidderName string 22 endpoint string 23 } 24 25 type SilverPushImpExt map[string]json.RawMessage 26 27 type SilverPushReqExt struct { 28 PublisherId string `json:"publisherId"` 29 BidFloor float64 `json:"bidfloor"` 30 } 31 32 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 33 imps := request.Imp 34 var errors []error 35 requests := make([]*adapters.RequestData, 0, len(imps)) 36 37 for _, imp := range imps { 38 impsByMediaType := impressionByMediaType(&imp) 39 40 request.Imp = []openrtb2.Imp{impsByMediaType} 41 42 if err := validateRequest(request); err != nil { 43 errors = append(errors, err) 44 continue 45 } 46 47 requestData, err := a.makeRequest(request) 48 if err != nil { 49 errors = append(errors, err) 50 continue 51 } 52 53 requests = append(requests, requestData) 54 55 } 56 return requests, errors 57 } 58 59 func (a *adapter) makeRequest(req *openrtb2.BidRequest) (*adapters.RequestData, error) { 60 reqJSON, err := json.Marshal(req) 61 if err != nil { 62 return nil, err 63 } 64 headers := http.Header{} 65 headers.Add("Content-Type", "application/json;charset=utf-8") 66 headers.Add("Accept", "application/json") 67 headers.Add("X-Openrtb-Version", "2.5") 68 return &adapters.RequestData{ 69 Method: "POST", 70 Uri: a.endpoint, 71 Body: reqJSON, 72 Headers: headers, 73 }, nil 74 } 75 76 func validateRequest(req *openrtb2.BidRequest) error { 77 imp := &req.Imp[0] 78 var silverPushExt openrtb_ext.ImpExtSilverpush 79 if err := setPublisherId(req, imp, &silverPushExt); err != nil { 80 return err 81 } 82 if err := setUser(req); err != nil { 83 return err 84 } 85 86 setDevice(req) 87 88 if err := setExtToRequest(req, silverPushExt.PublisherId); err != nil { 89 return err 90 } 91 return setImpForAdExchange(imp, &silverPushExt) 92 } 93 94 func setDevice(req *openrtb2.BidRequest) { 95 if req.Device == nil { 96 return 97 } 98 deviceCopy := *req.Device 99 if len(deviceCopy.UA) == 0 { 100 return 101 } 102 deviceCopy.OS = getOS(deviceCopy.UA) 103 if isMobile(deviceCopy.UA) { 104 deviceCopy.DeviceType = 1 105 } else if isCTV(deviceCopy.UA) { 106 deviceCopy.DeviceType = 3 107 } else { 108 deviceCopy.DeviceType = 2 109 } 110 111 req.Device = &deviceCopy 112 } 113 114 func setUser(req *openrtb2.BidRequest) error { 115 var extUser openrtb_ext.ExtUser 116 var userExtRaw map[string]json.RawMessage 117 118 if req.User != nil && req.User.Ext != nil { 119 if err := json.Unmarshal(req.User.Ext, &userExtRaw); err != nil { 120 return &errortypes.BadInput{Message: "Invalid user.ext."} 121 } 122 if userExtDataRaw, ok := userExtRaw["data"]; ok { 123 if err := json.Unmarshal(userExtDataRaw, &extUser); err != nil { 124 return &errortypes.BadInput{Message: "Invalid user.ext.data."} 125 } 126 var userCopy = *req.User 127 if isValidEids(extUser.Eids) { 128 userExt, err := json.Marshal( 129 &openrtb2.User{ 130 EIDs: extUser.Eids, 131 }) 132 if err != nil { 133 return &errortypes.BadInput{Message: "Error in marshaling user.eids."} 134 } 135 136 userCopy.Ext = userExt 137 req.User = &userCopy 138 } 139 } 140 } 141 return nil 142 } 143 144 func setExtToRequest(req *openrtb2.BidRequest, publisherID string) error { 145 record := map[string]string{ 146 "bc": bidderConfig + "_" + bidderVersion, 147 "publisherId": publisherID, 148 } 149 reqExt, err := json.Marshal(record) 150 if err != nil { 151 return err 152 } 153 req.Ext = reqExt 154 return nil 155 } 156 157 func setImpForAdExchange(imp *openrtb2.Imp, impExt *openrtb_ext.ImpExtSilverpush) error { 158 if impExt.BidFloor == 0 { 159 if imp.Banner != nil { 160 imp.BidFloor = 0.05 161 } else if imp.Video != nil { 162 imp.BidFloor = 0.1 163 } 164 } else { 165 imp.BidFloor = impExt.BidFloor 166 } 167 168 if imp.Banner != nil { 169 bannerCopy, err := setBannerDimension(imp.Banner) 170 if err != nil { 171 return err 172 } 173 imp.Banner = bannerCopy 174 } 175 176 if imp.Video != nil { 177 videoCopy, err := checkVideoDimension(imp.Video) 178 if err != nil { 179 return err 180 } 181 imp.Video = videoCopy 182 } 183 184 return nil 185 } 186 187 func checkVideoDimension(video *openrtb2.Video) (*openrtb2.Video, error) { 188 videoCopy := *video 189 if videoCopy.MaxDuration == 0 { 190 videoCopy.MaxDuration = 120 191 } 192 if videoCopy.MaxDuration < videoCopy.MinDuration { 193 videoCopy.MaxDuration = videoCopy.MinDuration 194 videoCopy.MinDuration = 0 195 } 196 if videoCopy.API == nil || videoCopy.MIMEs == nil || videoCopy.Protocols == nil || videoCopy.MinDuration < 0 { 197 return nil, &errortypes.BadInput{Message: "Invalid or missing video field(s)"} 198 } 199 return &videoCopy, nil 200 } 201 202 func setBannerDimension(banner *openrtb2.Banner) (*openrtb2.Banner, error) { 203 if banner.W != nil && banner.H != nil { 204 return banner, nil 205 } 206 if len(banner.Format) == 0 { 207 return banner, &errortypes.BadInput{Message: "No sizes provided for Banner."} 208 } 209 bannerCopy := *banner 210 bannerCopy.W = openrtb2.Int64Ptr(banner.Format[0].W) 211 bannerCopy.H = openrtb2.Int64Ptr(banner.Format[0].H) 212 213 return &bannerCopy, nil 214 } 215 216 func setPublisherId(req *openrtb2.BidRequest, imp *openrtb2.Imp, impExt *openrtb_ext.ImpExtSilverpush) error { 217 var bidderExt adapters.ExtImpBidder 218 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 219 return &errortypes.BadInput{ 220 Message: err.Error(), 221 } 222 } 223 224 if err := json.Unmarshal(bidderExt.Bidder, impExt); err != nil { 225 return &errortypes.BadInput{ 226 Message: err.Error(), 227 } 228 } 229 if impExt.PublisherId == "" { 230 return &errortypes.BadInput{Message: "Missing publisherId parameter."} 231 } 232 if req.Site != nil { 233 siteCopy := *req.Site 234 if siteCopy.Publisher == nil { 235 siteCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} 236 } else { 237 publisher := *siteCopy.Publisher 238 publisher.ID = impExt.PublisherId 239 siteCopy.Publisher = &publisher 240 } 241 req.Site = &siteCopy 242 243 } else if req.App != nil { 244 appCopy := *req.App 245 if appCopy.Publisher == nil { 246 appCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} 247 } else { 248 publisher := *appCopy.Publisher 249 publisher.ID = impExt.PublisherId 250 appCopy.Publisher = &publisher 251 } 252 appCopy.Publisher = &openrtb2.Publisher{ID: impExt.PublisherId} 253 req.App = &appCopy 254 255 } 256 257 return nil 258 } 259 260 func impressionByMediaType(imp *openrtb2.Imp) openrtb2.Imp { 261 impCopy := *imp 262 if imp.Banner != nil { 263 impCopy.Video = nil 264 } 265 if imp.Video != nil { 266 impCopy.Banner = nil 267 268 } 269 return impCopy 270 } 271 272 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 273 if adapters.IsResponseStatusCodeNoContent(response) { 274 return nil, nil 275 } 276 if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { 277 return nil, []error{&errortypes.BadInput{ 278 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 279 }} 280 } 281 282 var bidResp openrtb2.BidResponse 283 284 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 285 return nil, []error{err} 286 } 287 288 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) 289 290 // overrride default currency 291 if bidResp.Cur != "" { 292 bidResponse.Currency = bidResp.Cur 293 } 294 295 for _, sb := range bidResp.SeatBid { 296 for i := 0; i < len(sb.Bid); i++ { 297 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 298 Bid: &sb.Bid[i], 299 BidType: getMediaTypeForImp(sb.Bid[i]), 300 }) 301 } 302 } 303 return bidResponse, nil 304 } 305 306 // getMediaTypeForImp figures out which media type this bid is for. 307 // SilverPush doesn't support multi-type impressions. 308 // If both banner and video exist, take banner as we do not want in-banner video. 309 func getMediaTypeForImp(bid openrtb2.Bid) openrtb_ext.BidType { 310 switch bid.MType { 311 case openrtb2.MarkupBanner: 312 return openrtb_ext.BidTypeBanner 313 case openrtb2.MarkupVideo: 314 return openrtb_ext.BidTypeVideo 315 default: 316 return "" 317 } 318 } 319 320 // Builder builds a new instance of the silverpush adapter for the given bidder with the given config. 321 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 322 bidder := &adapter{ 323 endpoint: config.Endpoint, 324 bidderName: string(bidderName), 325 } 326 return bidder, nil 327 }