github.com/prebid/prebid-server/v2@v2.18.0/adapters/adhese/adhese.go (about) 1 package adhese 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "sort" 9 "strconv" 10 "strings" 11 "text/template" 12 13 "github.com/prebid/openrtb/v20/openrtb2" 14 "github.com/prebid/prebid-server/v2/adapters" 15 "github.com/prebid/prebid-server/v2/config" 16 "github.com/prebid/prebid-server/v2/errortypes" 17 "github.com/prebid/prebid-server/v2/macros" 18 "github.com/prebid/prebid-server/v2/openrtb_ext" 19 ) 20 21 type AdheseAdapter struct { 22 endpointTemplate *template.Template 23 } 24 25 func extractSlotParameter(parameters openrtb_ext.ExtImpAdhese) string { 26 return fmt.Sprintf("/sl%s-%s", url.PathEscape(parameters.Location), url.PathEscape(parameters.Format)) 27 } 28 29 func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string { 30 if len(parameters.Keywords) == 0 { 31 return "" 32 } 33 var parametersAsString = "" 34 var targetParsed map[string]interface{} 35 err := json.Unmarshal(parameters.Keywords, &targetParsed) 36 if err != nil { 37 return "" 38 } 39 40 targetKeys := make([]string, 0, len(targetParsed)) 41 for key := range targetParsed { 42 targetKeys = append(targetKeys, key) 43 } 44 sort.Strings(targetKeys) 45 46 for _, targetKey := range targetKeys { 47 var targetingValues = targetParsed[targetKey].([]interface{}) 48 parametersAsString += "/" + url.PathEscape(targetKey) 49 for _, targetRawValKey := range targetingValues { 50 var targetValueParsed = targetRawValKey.(string) 51 parametersAsString += targetValueParsed + ";" 52 } 53 parametersAsString = strings.TrimRight(parametersAsString, ";") 54 } 55 56 return parametersAsString 57 } 58 59 func extractGdprParameter(request *openrtb2.BidRequest) string { 60 if request.User != nil { 61 var extUser openrtb_ext.ExtUser 62 if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { 63 return "/xt" + extUser.Consent 64 } 65 } 66 return "" 67 } 68 69 func extractRefererParameter(request *openrtb2.BidRequest) string { 70 if request.Site != nil && request.Site.Page != "" { 71 return "/xf" + url.QueryEscape(request.Site.Page) 72 } 73 return "" 74 } 75 76 func extractIfaParameter(request *openrtb2.BidRequest) string { 77 if request.Device != nil && request.Device.IFA != "" { 78 return "/xz" + url.QueryEscape(request.Device.IFA) 79 } 80 return "" 81 } 82 83 func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 84 errs := make([]error, 0, len(request.Imp)) 85 86 var err error 87 88 // If all the requests are invalid, Call to adaptor is skipped 89 if len(request.Imp) == 0 { 90 errs = append(errs, WrapReqError("Imp is empty")) 91 return nil, errs 92 } 93 94 var imp = &request.Imp[0] 95 var bidderExt adapters.ExtImpBidder 96 97 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 98 errs = append(errs, WrapReqError("Request could not be parsed as ExtImpBidder due to: "+err.Error())) 99 return nil, errs 100 } 101 102 var params openrtb_ext.ExtImpAdhese 103 if err := json.Unmarshal(bidderExt.Bidder, ¶ms); err != nil { 104 errs = append(errs, WrapReqError("Request could not be parsed as ExtImpAdhese due to: "+err.Error())) 105 return nil, errs 106 } 107 108 // Compose url 109 endpointParams := macros.EndpointTemplateParams{AccountID: params.Account} 110 111 host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) 112 if err != nil { 113 errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error())) 114 return nil, errs 115 } 116 complete_url := fmt.Sprintf("%s%s%s%s%s%s", 117 host, 118 extractSlotParameter(params), 119 extractTargetParameters(params), 120 extractGdprParameter(request), 121 extractRefererParameter(request), 122 extractIfaParameter(request)) 123 124 return []*adapters.RequestData{{ 125 Method: "GET", 126 Uri: complete_url, 127 ImpIDs: []string{imp.ID}, 128 }}, errs 129 } 130 131 func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 132 if response.StatusCode == http.StatusNoContent { 133 return nil, nil 134 } else if response.StatusCode != http.StatusOK { 135 return nil, []error{WrapServerError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))} 136 } 137 138 var bidResponse openrtb2.BidResponse 139 140 var adheseBidResponseArray []AdheseBid 141 if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { 142 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed as generic Adhese bid.", string(response.Body)))} 143 } 144 145 if len(adheseBidResponseArray) == 0 { 146 return nil, nil 147 } 148 var adheseBid = adheseBidResponseArray[0] 149 150 if adheseBid.Origin == "JERLICIA" { 151 var extArray []AdheseExt 152 var originDataArray []AdheseOriginData 153 if err := json.Unmarshal(response.Body, &extArray); err != nil { 154 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA ext.", string(response.Body)))} 155 } 156 157 if err := json.Unmarshal(response.Body, &originDataArray); err != nil { 158 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA origin data.", string(response.Body)))} 159 } 160 bidResponse = convertAdheseBid(adheseBid, extArray[0], originDataArray[0]) 161 } else { 162 bidResponse = convertAdheseOpenRtbBid(adheseBid) 163 } 164 165 price, err := strconv.ParseFloat(adheseBid.Extension.Prebid.Cpm.Amount, 64) 166 if err != nil { 167 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Price %v as float ", string(adheseBid.Extension.Prebid.Cpm.Amount)))} 168 } 169 width, err := strconv.ParseInt(adheseBid.Width, 10, 64) 170 if err != nil { 171 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Width %v as int ", string(adheseBid.Width)))} 172 } 173 height, err := strconv.ParseInt(adheseBid.Height, 10, 64) 174 if err != nil { 175 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Height %v as int ", string(adheseBid.Height)))} 176 } 177 bidResponse.Cur = adheseBid.Extension.Prebid.Cpm.Currency 178 if len(bidResponse.SeatBid) > 0 && len(bidResponse.SeatBid[0].Bid) > 0 { 179 bidResponse.SeatBid[0].Bid[0].Price = price 180 bidResponse.SeatBid[0].Bid[0].W = width 181 bidResponse.SeatBid[0].Bid[0].H = height 182 } 183 184 bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5) 185 186 if len(bidResponse.SeatBid) == 0 { 187 return nil, []error{WrapServerError("Response resulted in an empty seatBid array.")} 188 } 189 190 var errs []error 191 for _, sb := range bidResponse.SeatBid { 192 for i := 0; i < len(sb.Bid); i++ { 193 bid := sb.Bid[i] 194 bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ 195 Bid: &bid, 196 BidType: getBidType(bid.AdM), 197 }) 198 199 } 200 } 201 return bidderResponse, errs 202 } 203 204 func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb2.BidResponse { 205 adheseExtJson, err := json.Marshal(adheseOriginData) 206 if err != nil { 207 adheseExtJson = make([]byte, 0) 208 } 209 return openrtb2.BidResponse{ 210 ID: adheseExt.Id, 211 SeatBid: []openrtb2.SeatBid{{ 212 Bid: []openrtb2.Bid{{ 213 DealID: adheseExt.OrderId, 214 CrID: adheseExt.Id, 215 AdM: getAdMarkup(adheseBid, adheseExt), 216 Ext: adheseExtJson, 217 }}, 218 Seat: "", 219 }}, 220 } 221 } 222 223 func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb2.BidResponse { 224 var response openrtb2.BidResponse = adheseBid.OriginData 225 if len(response.SeatBid) > 0 && len(response.SeatBid[0].Bid) > 0 { 226 response.SeatBid[0].Bid[0].AdM = adheseBid.Body 227 } 228 return response 229 } 230 231 func getAdMarkup(adheseBid AdheseBid, adheseExt AdheseExt) string { 232 if adheseExt.Ext == "js" { 233 if ContainsAny(adheseBid.Body, []string{"<script", "<div", "<html"}) { 234 counter := "" 235 if len(adheseExt.ImpressionCounter) > 0 { 236 counter = "<img src='" + adheseExt.ImpressionCounter + "' style='height:1px; width:1px; margin: -1px -1px; display:none;'/>" 237 } 238 return adheseBid.Body + counter 239 } 240 if ContainsAny(adheseBid.Body, []string{"<?xml", "<vast"}) { 241 return adheseBid.Body 242 } 243 } 244 return adheseExt.Tag 245 } 246 247 func getBidType(bidAdm string) openrtb_ext.BidType { 248 if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) { 249 return openrtb_ext.BidTypeVideo 250 } 251 return openrtb_ext.BidTypeBanner 252 } 253 254 func WrapReqError(errorStr string) *errortypes.BadInput { 255 return &errortypes.BadInput{Message: errorStr} 256 } 257 258 func WrapServerError(errorStr string) *errortypes.BadServerResponse { 259 return &errortypes.BadServerResponse{Message: errorStr} 260 } 261 262 func ContainsAny(raw string, keys []string) bool { 263 lowerCased := strings.ToLower(raw) 264 for i := 0; i < len(keys); i++ { 265 if strings.Contains(lowerCased, keys[i]) { 266 return true 267 } 268 } 269 return false 270 271 } 272 273 // Builder builds a new instance of the Adhese adapter for the given bidder with the given config. 274 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 275 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 276 if err != nil { 277 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 278 } 279 280 bidder := &AdheseAdapter{ 281 endpointTemplate: template, 282 } 283 return bidder, nil 284 }