github.com/prebid/prebid-server/v2@v2.18.0/adapters/invibes/invibes.go (about) 1 package invibes 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 "text/template" 11 12 "github.com/prebid/openrtb/v20/openrtb2" 13 "github.com/prebid/prebid-server/v2/adapters" 14 "github.com/prebid/prebid-server/v2/config" 15 "github.com/prebid/prebid-server/v2/errortypes" 16 "github.com/prebid/prebid-server/v2/macros" 17 "github.com/prebid/prebid-server/v2/metrics" 18 "github.com/prebid/prebid-server/v2/openrtb_ext" 19 ) 20 21 const adapterVersion = "prebid_1.0.0" 22 const invibesBidVersion = "4" 23 24 type InvibesAdRequest struct { 25 BidParamsJson string `json:"BidParamsJson"` 26 Location string `json:"Location"` 27 Lid string `json:"Lid"` 28 IsTestBid bool `json:"IsTestBid"` 29 Kw string `json:"Kw"` 30 IsAMP bool `json:"IsAmp"` 31 Width string `json:"Width"` 32 Height string `json:"Height"` 33 GDPRConsent string `json:"GdprConsent"` 34 GDPR bool `json:"Gdpr"` 35 Bvid string `json:"Bvid"` 36 InvibBVLog bool `json:"InvibBVLog"` 37 VideoAdDebug bool `json:"VideoAdDebug"` 38 } 39 type InvibesBidParams struct { 40 PlacementIDs []string `json:"PlacementIds"` 41 BidVersion string `json:"BidVersion"` 42 Properties map[string]InvibesPlacementProperty `json:"Properties"` 43 } 44 type InvibesPlacementProperty struct { 45 Formats []openrtb2.Format `json:"Formats"` 46 ImpID string `json:"ImpId"` 47 } 48 type InvibesInternalParams struct { 49 BidParams InvibesBidParams 50 DomainID int 51 IsAMP bool 52 GDPR bool 53 GDPRConsent string 54 55 TestBvid string 56 TestLog bool 57 } 58 type BidServerBidderResponse struct { 59 Currency string `json:"currency"` 60 TypedBids []BidServerTypedBid `json:"typedBids"` 61 Error string `json:"error"` 62 } 63 type BidServerTypedBid struct { 64 Bid openrtb2.Bid `json:"bid"` 65 DealPriority int `json:"dealPriority"` 66 } 67 68 func (a *InvibesInternalParams) IsTestRequest() bool { 69 return a.TestBvid != "" 70 } 71 72 type InvibesAdapter struct { 73 EndpointTemplate *template.Template 74 } 75 76 // Builder builds a new instance of the Invibes adapter for the given bidder with the given config. 77 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 78 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 79 if err != nil { 80 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 81 } 82 83 bidder := InvibesAdapter{ 84 EndpointTemplate: template, 85 } 86 return &bidder, nil 87 } 88 89 func (a *InvibesAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 90 var httpRequests []*adapters.RequestData 91 var tempErrors []error 92 gdprApplies, consentString := readGDPR(request) 93 94 var invibesInternalParams InvibesInternalParams = InvibesInternalParams{ 95 BidParams: InvibesBidParams{ 96 Properties: make(map[string]InvibesPlacementProperty), 97 BidVersion: invibesBidVersion, 98 }, 99 } 100 101 for _, imp := range request.Imp { 102 var bidderExt adapters.ExtImpBidder 103 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 104 tempErrors = append(tempErrors, &errortypes.BadInput{ 105 Message: "Error parsing bidderExt object", 106 }) 107 continue 108 } 109 var invibesExt openrtb_ext.ExtImpInvibes 110 if err := json.Unmarshal(bidderExt.Bidder, &invibesExt); err != nil { 111 tempErrors = append(tempErrors, &errortypes.BadInput{ 112 Message: "Error parsing invibesExt parameters", 113 }) 114 continue 115 } 116 if imp.Banner == nil { 117 tempErrors = append(tempErrors, &errortypes.BadInput{ 118 Message: "Banner not specified", 119 }) 120 continue 121 } 122 123 adFormats := readAdFormats(*imp.Banner) 124 125 invibesInternalParams.DomainID = invibesExt.DomainID 126 invibesInternalParams.BidParams.PlacementIDs = append(invibesInternalParams.BidParams.PlacementIDs, strings.TrimSpace(invibesExt.PlacementID)) 127 invibesInternalParams.BidParams.Properties[invibesExt.PlacementID] = InvibesPlacementProperty{ 128 ImpID: imp.ID, 129 Formats: adFormats, 130 } 131 if invibesExt.Debug.TestBvid != "" { 132 invibesInternalParams.TestBvid = invibesExt.Debug.TestBvid 133 } 134 invibesInternalParams.TestLog = invibesExt.Debug.TestLog 135 } 136 if reqInfo.PbsEntryPoint == metrics.ReqTypeAMP { 137 invibesInternalParams.IsAMP = true 138 } 139 140 if len(invibesInternalParams.BidParams.PlacementIDs) == 0 { 141 return nil, tempErrors 142 } 143 144 var finalErrors []error 145 invibesInternalParams.GDPR = gdprApplies 146 invibesInternalParams.GDPRConsent = consentString 147 148 newHttpRequest, err := a.makeRequest(invibesInternalParams, reqInfo, httpRequests, request) 149 if err != nil { 150 finalErrors = append(finalErrors, err) 151 } else if newHttpRequest != nil { 152 httpRequests = append(httpRequests, newHttpRequest) 153 } 154 155 return httpRequests, finalErrors 156 } 157 158 func readGDPR(request *openrtb2.BidRequest) (bool, string) { 159 consentString := "" 160 if request.User != nil { 161 var extUser openrtb_ext.ExtUser 162 if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { 163 consentString = extUser.Consent 164 } 165 } 166 gdprApplies := true 167 var extRegs openrtb_ext.ExtRegs 168 if request.Regs != nil { 169 if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { 170 if extRegs.GDPR != nil { 171 gdprApplies = (*extRegs.GDPR == 1) 172 } 173 } 174 } 175 return gdprApplies, consentString 176 } 177 178 func readAdFormats(currentBanner openrtb2.Banner) []openrtb2.Format { 179 var adFormats []openrtb2.Format 180 if currentBanner.Format != nil { 181 adFormats = currentBanner.Format 182 } else if currentBanner.W != nil && currentBanner.H != nil { 183 adFormats = []openrtb2.Format{ 184 { 185 W: *currentBanner.W, 186 H: *currentBanner.H, 187 }, 188 } 189 } 190 return adFormats 191 } 192 193 func (a *InvibesAdapter) makeRequest(invibesParams InvibesInternalParams, reqInfo *adapters.ExtraRequestInfo, existingRequests []*adapters.RequestData, request *openrtb2.BidRequest) (*adapters.RequestData, error) { 194 195 url, err := a.makeURL(request, invibesParams.DomainID) 196 if err != nil { 197 return nil, err 198 } 199 parameter, errp := a.makeParameter(invibesParams, request) 200 if errp != nil { 201 return nil, errp 202 } 203 body, errm := json.Marshal(parameter) 204 if errm != nil { 205 return nil, errm 206 } 207 208 headers := http.Header{} 209 headers.Add("Content-Type", "application/json;charset=utf-8") 210 headers.Add("Accept", "application/json") 211 212 if request.Device != nil { 213 headers.Add("User-Agent", request.Device.UA) 214 } 215 216 if request.Device != nil { 217 if request.Device.IP != "" { 218 headers.Add("X-Forwarded-For", request.Device.IP) 219 } else if request.Device.IPv6 != "" { 220 headers.Add("X-Forwarded-For", request.Device.IPv6) 221 } 222 } 223 if request.Site != nil { 224 headers.Add("Referer", request.Site.Page) 225 } 226 headers.Add("Aver", adapterVersion) 227 228 return &adapters.RequestData{ 229 Method: "POST", 230 Uri: url, 231 Headers: headers, 232 Body: body, 233 ImpIDs: getImpIDs(invibesParams.BidParams.Properties), 234 }, nil 235 } 236 237 func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, request *openrtb2.BidRequest) (*InvibesAdRequest, error) { 238 var lid string = "" 239 if request.User != nil && request.User.BuyerUID != "" { 240 lid = request.User.BuyerUID 241 } 242 if request.Site == nil { 243 return nil, &errortypes.BadInput{ 244 Message: "Site not specified", 245 } 246 } 247 248 var width, height string 249 if request.Device != nil { 250 if request.Device.W > 0 { 251 width = strconv.FormatInt(request.Device.W, 10) 252 } 253 254 if request.Device.H > 0 { 255 height = strconv.FormatInt(request.Device.H, 10) 256 } 257 } 258 259 var invRequest InvibesAdRequest 260 bidParamsJson, err := json.Marshal(invibesParams.BidParams) 261 if err == nil { 262 invRequest = InvibesAdRequest{ 263 IsTestBid: invibesParams.IsTestRequest(), 264 BidParamsJson: string(bidParamsJson), 265 Location: request.Site.Page, 266 Lid: lid, 267 Kw: request.Site.Keywords, 268 IsAMP: invibesParams.IsAMP, 269 Width: width, 270 Height: height, 271 GDPRConsent: invibesParams.GDPRConsent, 272 GDPR: invibesParams.GDPR, 273 Bvid: invibesParams.TestBvid, 274 InvibBVLog: invibesParams.TestLog, 275 VideoAdDebug: invibesParams.TestLog, 276 } 277 } 278 279 return &invRequest, err 280 } 281 282 func (a *InvibesAdapter) makeURL(request *openrtb2.BidRequest, domainID int) (string, error) { 283 var subdomain string 284 if domainID == 0 || domainID == 1 || domainID == 1001 { 285 subdomain = "bid" 286 } else if domainID < 1002 { 287 subdomain = "bid" + strconv.Itoa(domainID) 288 } else { 289 subdomain = "bid" + strconv.Itoa(domainID-1000) 290 } 291 292 var endpointURL *url.URL 293 endpointParams := macros.EndpointTemplateParams{ZoneID: subdomain} 294 host, err := macros.ResolveMacros(a.EndpointTemplate, endpointParams) 295 296 if err == nil { 297 endpointURL, err = url.Parse(host) 298 } 299 if err != nil { 300 return "", &errortypes.BadInput{ 301 Message: "Unable to parse url template: " + err.Error(), 302 } 303 } 304 305 return endpointURL.String(), nil 306 } 307 308 func (a *InvibesAdapter) MakeBids( 309 internalRequest *openrtb2.BidRequest, 310 externalRequest *adapters.RequestData, 311 response *adapters.ResponseData, 312 ) (*adapters.BidderResponse, []error) { 313 if response.StatusCode != http.StatusOK { 314 return nil, []error{fmt.Errorf("Unexpected status code: %d.", response.StatusCode)} 315 } 316 317 bidResponse := BidServerBidderResponse{} 318 if err := json.Unmarshal(response.Body, &bidResponse); err != nil { 319 return nil, []error{err} 320 } 321 322 var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.TypedBids)) 323 var errors []error 324 parsedResponses.Currency = bidResponse.Currency 325 326 if bidResponse.Error != "" { 327 return nil, []error{fmt.Errorf("Server error: %s.", bidResponse.Error)} 328 } 329 for _, typedbid := range bidResponse.TypedBids { 330 bid := typedbid.Bid 331 parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{ 332 Bid: &bid, 333 BidType: openrtb_ext.BidTypeBanner, 334 DealPriority: typedbid.DealPriority, 335 }) 336 } 337 338 return parsedResponses, errors 339 } 340 341 func getImpIDs(bidParamsProperties map[string]InvibesPlacementProperty) []string { 342 impIDs := make([]string, 0, len(bidParamsProperties)) 343 for i := range bidParamsProperties { 344 impIDs = append(impIDs, bidParamsProperties[i].ImpID) 345 } 346 return impIDs 347 }