github.com/prebid/prebid-server/v2@v2.18.0/adapters/flipp/flipp.go (about) 1 package flipp 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "net/url" 10 "strings" 11 12 "github.com/buger/jsonparser" 13 "github.com/prebid/go-gdpr/consentconstants" 14 "github.com/prebid/go-gdpr/vendorconsent" 15 "github.com/prebid/openrtb/v20/openrtb2" 16 "github.com/prebid/prebid-server/v2/adapters" 17 "github.com/prebid/prebid-server/v2/config" 18 "github.com/prebid/prebid-server/v2/openrtb_ext" 19 "github.com/prebid/prebid-server/v2/util/uuidutil" 20 ) 21 22 const ( 23 bannerType = "banner" 24 inlineDivName = "inline" 25 flippBidder = "flipp" 26 defaultCurrency = "USD" 27 ) 28 29 var ( 30 count int64 = 1 31 adTypes = []int64{4309, 641} 32 dtxTypes = []int64{5061} 33 ) 34 35 type adapter struct { 36 endpoint string 37 uuidGenerator uuidutil.UUIDGenerator 38 } 39 40 var ( 41 errRequestEmpty = errors.New("adapterRequest is empty") 42 ) 43 44 // Builder builds a new instance of the Flipp adapter for the given bidder with the given config. 45 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 46 bidder := &adapter{ 47 endpoint: config.Endpoint, 48 uuidGenerator: uuidutil.UUIDRandomGenerator{}, 49 } 50 return bidder, nil 51 } 52 53 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 54 adapterRequests := make([]*adapters.RequestData, 0, len(request.Imp)) 55 var errors []error 56 57 for _, imp := range request.Imp { 58 adapterReq, err := a.processImp(request, imp) 59 if err != nil { 60 errors = append(errors, err) 61 continue 62 } 63 adapterRequests = append(adapterRequests, adapterReq) 64 } 65 if len(adapterRequests) == 0 { 66 return nil, append(errors, errRequestEmpty) 67 } 68 return adapterRequests, errors 69 } 70 71 func (a *adapter) makeRequest(request *openrtb2.BidRequest, campaignRequestBody CampaignRequestBody, impID string) (*adapters.RequestData, error) { 72 campaignRequestBodyJSON, err := json.Marshal(campaignRequestBody) 73 if err != nil { 74 return nil, err 75 } 76 77 headers := http.Header{} 78 headers.Add("Content-Type", "application/json") 79 if request.Device != nil && request.Device.UA != "" { 80 headers.Add("User-Agent", request.Device.UA) 81 } 82 return &adapters.RequestData{ 83 Method: http.MethodPost, 84 Uri: a.endpoint, 85 Body: campaignRequestBodyJSON, 86 Headers: headers, 87 ImpIDs: []string{impID}, 88 }, err 89 } 90 91 func (a *adapter) processImp(request *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { 92 var flippExtParams openrtb_ext.ImpExtFlipp 93 params, _, _, err := jsonparser.Get(imp.Ext, "bidder") 94 if err != nil { 95 return nil, fmt.Errorf("flipp params not found. %v", err) 96 } 97 err = json.Unmarshal(params, &flippExtParams) 98 if err != nil { 99 return nil, fmt.Errorf("unable to extract flipp params. %v", err) 100 } 101 102 publisherUrl, err := url.Parse(request.Site.Page) 103 if err != nil { 104 return nil, fmt.Errorf("unable to parse site url. %v", err) 105 } 106 107 var contentCode string 108 if flippExtParams.Options.ContentCode != "" { 109 contentCode = flippExtParams.Options.ContentCode 110 } else if publisherUrl != nil { 111 contentCode = publisherUrl.Query().Get("flipp-content-code") 112 } 113 114 placement := Placement{ 115 DivName: inlineDivName, 116 SiteID: &flippExtParams.SiteID, 117 AdTypes: getAdTypes(flippExtParams.CreativeType), 118 ZoneIds: flippExtParams.ZoneIds, 119 Count: &count, 120 Prebid: buildPrebidRequest(flippExtParams, request, imp), 121 Properties: &Properties{ 122 ContentCode: &contentCode, 123 }, 124 Options: flippExtParams.Options, 125 } 126 127 var userIP string 128 if request.Device != nil && request.Device.IP != "" { 129 userIP = request.Device.IP 130 } else { 131 return nil, fmt.Errorf("no IP set in flipp bidder params or request device") 132 } 133 134 var userKey string 135 if request.User != nil && request.User.ID != "" { 136 userKey = request.User.ID 137 } else if flippExtParams.UserKey != "" && paramsUserKeyPermitted(request) { 138 userKey = flippExtParams.UserKey 139 } else { 140 uid, err := a.uuidGenerator.Generate() 141 if err != nil { 142 return nil, fmt.Errorf("unable to generate user uuid. %v", err) 143 } 144 userKey = uid 145 } 146 147 keywordsArray := strings.Split(request.Site.Keywords, ",") 148 149 campaignRequestBody := CampaignRequestBody{ 150 Placements: []*Placement{&placement}, 151 URL: request.Site.Page, 152 Keywords: keywordsArray, 153 IP: userIP, 154 User: &CampaignRequestBodyUser{ 155 Key: &userKey, 156 }, 157 } 158 159 adapterReq, err := a.makeRequest(request, campaignRequestBody, imp.ID) 160 if err != nil { 161 return nil, fmt.Errorf("make request failed with err %v", err) 162 } 163 164 return adapterReq, nil 165 } 166 167 func buildPrebidRequest(flippExtParams openrtb_ext.ImpExtFlipp, request *openrtb2.BidRequest, imp openrtb2.Imp) *PrebidRequest { 168 var height int64 169 var width int64 170 if imp.Banner != nil && len(imp.Banner.Format) > 0 { 171 height = imp.Banner.Format[0].H 172 width = imp.Banner.Format[0].W 173 } 174 prebidRequest := PrebidRequest{ 175 CreativeType: &flippExtParams.CreativeType, 176 PublisherNameIdentifier: &flippExtParams.PublisherNameIdentifier, 177 RequestID: &imp.ID, 178 Height: &height, 179 Width: &width, 180 } 181 return &prebidRequest 182 } 183 184 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 185 if adapters.IsResponseStatusCodeNoContent(responseData) { 186 return nil, nil 187 } 188 189 if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { 190 return nil, []error{err} 191 } 192 193 var campaignResponseBody CampaignResponseBody 194 if err := json.Unmarshal(responseData.Body, &campaignResponseBody); err != nil { 195 return nil, []error{err} 196 } 197 198 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 199 bidResponse.Currency = defaultCurrency 200 for _, imp := range request.Imp { 201 for _, decision := range campaignResponseBody.Decisions.Inline { 202 if *decision.Prebid.RequestID == imp.ID { 203 b := &adapters.TypedBid{ 204 Bid: buildBid(decision, imp.ID), 205 BidType: openrtb_ext.BidType(bannerType), 206 } 207 bidResponse.Bids = append(bidResponse.Bids, b) 208 } 209 } 210 } 211 return bidResponse, nil 212 } 213 214 func getAdTypes(creativeType string) []int64 { 215 if creativeType == "DTX" { 216 return dtxTypes 217 } 218 return adTypes 219 } 220 221 func buildBid(decision *InlineModel, impId string) *openrtb2.Bid { 222 bid := &openrtb2.Bid{ 223 CrID: fmt.Sprint(decision.CreativeID), 224 Price: *decision.Prebid.Cpm, 225 AdM: *decision.Prebid.Creative, 226 ID: fmt.Sprint(decision.AdID), 227 ImpID: impId, 228 } 229 if len(decision.Contents) > 0 || decision.Contents[0] != nil || decision.Contents[0].Data != nil { 230 if decision.Contents[0].Data.Width != 0 { 231 bid.W = decision.Contents[0].Data.Width 232 } 233 bid.H = 0 234 } 235 return bid 236 } 237 238 func paramsUserKeyPermitted(request *openrtb2.BidRequest) bool { 239 if request.Regs != nil { 240 if request.Regs.COPPA == 1 { 241 return false 242 } 243 if request.Regs.GDPR != nil && *request.Regs.GDPR == 1 { 244 return false 245 } 246 } 247 if request.Ext != nil { 248 var extData struct { 249 TransmitEids *bool `json:"transmitEids,omitempty"` 250 } 251 if err := json.Unmarshal(request.Ext, &extData); err == nil { 252 if extData.TransmitEids != nil && !*extData.TransmitEids { 253 return false 254 } 255 } 256 } 257 if request.User != nil && request.User.Consent != "" { 258 data, err := base64.RawURLEncoding.DecodeString(request.User.Consent) 259 if err != nil { 260 return true 261 } 262 consent, err := vendorconsent.Parse(data) 263 if err != nil { 264 return true 265 } 266 if !consent.PurposeAllowed(consentconstants.ContentSelectionDeliveryReporting) { 267 return false 268 } 269 } 270 return true 271 }