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, &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  	}}, 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  }