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, &params); 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  }