github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 13 "github.com/prebid/prebid-server/adapters" 14 "github.com/prebid/prebid-server/config" 15 "github.com/prebid/prebid-server/errortypes" 16 "github.com/prebid/prebid-server/macros" 17 "github.com/prebid/prebid-server/metrics" 18 "github.com/prebid/prebid-server/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 }, nil 234 } 235 236 func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, request *openrtb2.BidRequest) (*InvibesAdRequest, error) { 237 var lid string = "" 238 if request.User != nil && request.User.BuyerUID != "" { 239 lid = request.User.BuyerUID 240 } 241 if request.Site == nil { 242 return nil, &errortypes.BadInput{ 243 Message: "Site not specified", 244 } 245 } 246 247 var width, height string 248 if request.Device != nil { 249 if request.Device.W > 0 { 250 width = strconv.FormatInt(request.Device.W, 10) 251 } 252 253 if request.Device.H > 0 { 254 height = strconv.FormatInt(request.Device.H, 10) 255 } 256 } 257 258 var invRequest InvibesAdRequest 259 bidParamsJson, err := json.Marshal(invibesParams.BidParams) 260 if err == nil { 261 invRequest = InvibesAdRequest{ 262 IsTestBid: invibesParams.IsTestRequest(), 263 BidParamsJson: string(bidParamsJson), 264 Location: request.Site.Page, 265 Lid: lid, 266 Kw: request.Site.Keywords, 267 IsAMP: invibesParams.IsAMP, 268 Width: width, 269 Height: height, 270 GDPRConsent: invibesParams.GDPRConsent, 271 GDPR: invibesParams.GDPR, 272 Bvid: invibesParams.TestBvid, 273 InvibBVLog: invibesParams.TestLog, 274 VideoAdDebug: invibesParams.TestLog, 275 } 276 } 277 278 return &invRequest, err 279 } 280 281 func (a *InvibesAdapter) makeURL(request *openrtb2.BidRequest, domainID int) (string, error) { 282 var subdomain string 283 if domainID == 0 || domainID == 1 || domainID == 1001 { 284 subdomain = "bid" 285 } else if domainID < 1002 { 286 subdomain = "bid" + strconv.Itoa(domainID) 287 } else { 288 subdomain = "bid" + strconv.Itoa(domainID-1000) 289 } 290 291 var endpointURL *url.URL 292 endpointParams := macros.EndpointTemplateParams{ZoneID: subdomain} 293 host, err := macros.ResolveMacros(a.EndpointTemplate, endpointParams) 294 295 if err == nil { 296 endpointURL, err = url.Parse(host) 297 } 298 if err != nil { 299 return "", &errortypes.BadInput{ 300 Message: "Unable to parse url template: " + err.Error(), 301 } 302 } 303 304 return endpointURL.String(), nil 305 } 306 307 func (a *InvibesAdapter) MakeBids( 308 internalRequest *openrtb2.BidRequest, 309 externalRequest *adapters.RequestData, 310 response *adapters.ResponseData, 311 ) (*adapters.BidderResponse, []error) { 312 if response.StatusCode != http.StatusOK { 313 return nil, []error{fmt.Errorf("Unexpected status code: %d.", response.StatusCode)} 314 } 315 316 bidResponse := BidServerBidderResponse{} 317 if err := json.Unmarshal(response.Body, &bidResponse); err != nil { 318 return nil, []error{err} 319 } 320 321 var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.TypedBids)) 322 var errors []error 323 parsedResponses.Currency = bidResponse.Currency 324 325 if bidResponse.Error != "" { 326 return nil, []error{fmt.Errorf("Server error: %s.", bidResponse.Error)} 327 } 328 for _, typedbid := range bidResponse.TypedBids { 329 bid := typedbid.Bid 330 parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{ 331 Bid: &bid, 332 BidType: openrtb_ext.BidTypeBanner, 333 DealPriority: typedbid.DealPriority, 334 }) 335 } 336 337 return parsedResponses, errors 338 }