github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 14 "github.com/prebid/prebid-server/adapters" 15 "github.com/prebid/prebid-server/config" 16 "github.com/prebid/prebid-server/errortypes" 17 "github.com/prebid/prebid-server/macros" 18 "github.com/prebid/prebid-server/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 }}, errs 128 } 129 130 func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 131 if response.StatusCode == http.StatusNoContent { 132 return nil, nil 133 } else if response.StatusCode != http.StatusOK { 134 return nil, []error{WrapServerError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))} 135 } 136 137 var bidResponse openrtb2.BidResponse 138 139 var adheseBidResponseArray []AdheseBid 140 if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { 141 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed as generic Adhese bid.", string(response.Body)))} 142 } 143 144 var adheseBid = adheseBidResponseArray[0] 145 146 if adheseBid.Origin == "JERLICIA" { 147 var extArray []AdheseExt 148 var originDataArray []AdheseOriginData 149 if err := json.Unmarshal(response.Body, &extArray); err != nil { 150 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA ext.", string(response.Body)))} 151 } 152 153 if err := json.Unmarshal(response.Body, &originDataArray); err != nil { 154 return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA origin data.", string(response.Body)))} 155 } 156 bidResponse = convertAdheseBid(adheseBid, extArray[0], originDataArray[0]) 157 } else { 158 bidResponse = convertAdheseOpenRtbBid(adheseBid) 159 } 160 161 price, err := strconv.ParseFloat(adheseBid.Extension.Prebid.Cpm.Amount, 64) 162 if err != nil { 163 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Price %v as float ", string(adheseBid.Extension.Prebid.Cpm.Amount)))} 164 } 165 width, err := strconv.ParseInt(adheseBid.Width, 10, 64) 166 if err != nil { 167 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Width %v as int ", string(adheseBid.Width)))} 168 } 169 height, err := strconv.ParseInt(adheseBid.Height, 10, 64) 170 if err != nil { 171 return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Height %v as int ", string(adheseBid.Height)))} 172 } 173 bidResponse.Cur = adheseBid.Extension.Prebid.Cpm.Currency 174 if len(bidResponse.SeatBid) > 0 && len(bidResponse.SeatBid[0].Bid) > 0 { 175 bidResponse.SeatBid[0].Bid[0].Price = price 176 bidResponse.SeatBid[0].Bid[0].W = width 177 bidResponse.SeatBid[0].Bid[0].H = height 178 } 179 180 bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5) 181 182 if len(bidResponse.SeatBid) == 0 { 183 return nil, []error{WrapServerError("Response resulted in an empty seatBid array.")} 184 } 185 186 var errs []error 187 for _, sb := range bidResponse.SeatBid { 188 for i := 0; i < len(sb.Bid); i++ { 189 bid := sb.Bid[i] 190 bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ 191 Bid: &bid, 192 BidType: getBidType(bid.AdM), 193 }) 194 195 } 196 } 197 return bidderResponse, errs 198 } 199 200 func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb2.BidResponse { 201 adheseExtJson, err := json.Marshal(adheseOriginData) 202 if err != nil { 203 adheseExtJson = make([]byte, 0) 204 } 205 return openrtb2.BidResponse{ 206 ID: adheseExt.Id, 207 SeatBid: []openrtb2.SeatBid{{ 208 Bid: []openrtb2.Bid{{ 209 DealID: adheseExt.OrderId, 210 CrID: adheseExt.Id, 211 AdM: getAdMarkup(adheseBid, adheseExt), 212 Ext: adheseExtJson, 213 }}, 214 Seat: "", 215 }}, 216 } 217 } 218 219 func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb2.BidResponse { 220 var response openrtb2.BidResponse = adheseBid.OriginData 221 if len(response.SeatBid) > 0 && len(response.SeatBid[0].Bid) > 0 { 222 response.SeatBid[0].Bid[0].AdM = adheseBid.Body 223 } 224 return response 225 } 226 227 func getAdMarkup(adheseBid AdheseBid, adheseExt AdheseExt) string { 228 if adheseExt.Ext == "js" { 229 if ContainsAny(adheseBid.Body, []string{"<script", "<div", "<html"}) { 230 counter := "" 231 if len(adheseExt.ImpressionCounter) > 0 { 232 counter = "<img src='" + adheseExt.ImpressionCounter + "' style='height:1px; width:1px; margin: -1px -1px; display:none;'/>" 233 } 234 return adheseBid.Body + counter 235 } 236 if ContainsAny(adheseBid.Body, []string{"<?xml", "<vast"}) { 237 return adheseBid.Body 238 } 239 } 240 return adheseExt.Tag 241 } 242 243 func getBidType(bidAdm string) openrtb_ext.BidType { 244 if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) { 245 return openrtb_ext.BidTypeVideo 246 } 247 return openrtb_ext.BidTypeBanner 248 } 249 250 func WrapReqError(errorStr string) *errortypes.BadInput { 251 return &errortypes.BadInput{Message: errorStr} 252 } 253 254 func WrapServerError(errorStr string) *errortypes.BadServerResponse { 255 return &errortypes.BadServerResponse{Message: errorStr} 256 } 257 258 func ContainsAny(raw string, keys []string) bool { 259 lowerCased := strings.ToLower(raw) 260 for i := 0; i < len(keys); i++ { 261 if strings.Contains(lowerCased, keys[i]) { 262 return true 263 } 264 } 265 return false 266 267 } 268 269 // Builder builds a new instance of the Adhese adapter for the given bidder with the given config. 270 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 271 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 272 if err != nil { 273 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 274 } 275 276 bidder := &AdheseAdapter{ 277 endpointTemplate: template, 278 } 279 return bidder, nil 280 }