github.com/prebid/prebid-server/v2@v2.18.0/adapters/taboola/taboola.go (about)

     1  package taboola
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/prebid/openrtb/v20/adcom1"
    12  	"github.com/prebid/openrtb/v20/openrtb2"
    13  
    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 adapter struct {
    22  	endpoint *template.Template
    23  	gvlID    string
    24  }
    25  
    26  // Builder builds a new instance of Taboola adapter for the given bidder with the given config.
    27  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    28  	endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint)
    29  	if err != nil {
    30  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    31  	}
    32  
    33  	gvlID := ""
    34  	if server.GvlID > 0 {
    35  		gvlID = strconv.Itoa(server.GvlID)
    36  	}
    37  
    38  	bidder := &adapter{
    39  		endpoint: endpointTemplate,
    40  		gvlID:    gvlID,
    41  	}
    42  	return bidder, nil
    43  }
    44  
    45  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    46  
    47  	var requests []*adapters.RequestData
    48  
    49  	taboolaRequests, errs := createTaboolaRequests(request)
    50  	if len(errs) > 0 {
    51  		return nil, errs
    52  	}
    53  
    54  	for _, taboolaRequest := range taboolaRequests {
    55  		if len(taboolaRequest.Imp) > 0 {
    56  			request, err := a.buildRequest(taboolaRequest)
    57  			if err != nil {
    58  				return nil, []error{fmt.Errorf("unable to build request %v", err)}
    59  			}
    60  			requests = append(requests, request)
    61  		}
    62  	}
    63  
    64  	return requests, errs
    65  }
    66  
    67  func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
    68  	var errs []error
    69  
    70  	if responseData.StatusCode == http.StatusNoContent {
    71  		return nil, nil
    72  	}
    73  
    74  	if responseData.StatusCode == http.StatusBadRequest {
    75  		err := &errortypes.BadInput{
    76  			Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
    77  		}
    78  		return nil, []error{err}
    79  	}
    80  
    81  	if responseData.StatusCode != http.StatusOK {
    82  		err := &errortypes.BadServerResponse{
    83  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
    84  		}
    85  		return nil, []error{err}
    86  	}
    87  
    88  	var response openrtb2.BidResponse
    89  	if err := json.Unmarshal(responseData.Body, &response); err != nil {
    90  		return nil, []error{err}
    91  	}
    92  
    93  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
    94  	bidResponse.Currency = response.Cur
    95  	for _, seatBid := range response.SeatBid {
    96  		for i := range seatBid.Bid {
    97  			bidType, err := getMediaType(seatBid.Bid[i].ImpID, request.Imp)
    98  			resolveMacros(&seatBid.Bid[i])
    99  			if err != nil {
   100  				errs = append(errs, err)
   101  				continue
   102  			}
   103  			b := &adapters.TypedBid{
   104  				Bid:     &seatBid.Bid[i],
   105  				BidType: bidType,
   106  			}
   107  			bidResponse.Bids = append(bidResponse.Bids, b)
   108  		}
   109  	}
   110  	return bidResponse, errs
   111  }
   112  
   113  func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
   114  	requestJSON, err := json.Marshal(request)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	const (
   120  		NATIVE_ENDPOINT_PREFIX  = "native"
   121  		DISPLAY_ENDPOINT_PREFIX = "display"
   122  	)
   123  
   124  	//set MediaType based on first imp
   125  	var mediaType string
   126  	if request.Imp[0].Banner != nil {
   127  		mediaType = DISPLAY_ENDPOINT_PREFIX
   128  	} else if request.Imp[0].Native != nil {
   129  		mediaType = NATIVE_ENDPOINT_PREFIX
   130  	} else {
   131  		return nil, fmt.Errorf("unsupported media type for imp: %v", request.Imp[0])
   132  	}
   133  
   134  	url, err := a.buildEndpointURL(request.Site.ID, mediaType)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	requestData := &adapters.RequestData{
   140  		Method: "POST",
   141  		Uri:    url,
   142  		Body:   requestJSON,
   143  		ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
   144  	}
   145  
   146  	return requestData, nil
   147  }
   148  
   149  // Builds endpoint url based on adapter-specific pub settings from imp.ext
   150  func (a *adapter) buildEndpointURL(publisherId string, mediaType string) (string, error) {
   151  	endpointParams := macros.EndpointTemplateParams{PublisherID: publisherId, MediaType: mediaType, GvlID: a.gvlID}
   152  	resolvedUrl, err := macros.ResolveMacros(a.endpoint, endpointParams)
   153  	if err != nil {
   154  		return "", err
   155  	}
   156  	return resolvedUrl, nil
   157  }
   158  
   159  func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*openrtb2.BidRequest, errors []error) {
   160  	modifiedRequest := *request
   161  	var nativeImp []openrtb2.Imp
   162  	var bannerImp []openrtb2.Imp
   163  	var errs []error
   164  
   165  	var taboolaExt openrtb_ext.ImpExtTaboola
   166  	for i := 0; i < len(modifiedRequest.Imp); i++ {
   167  		imp := modifiedRequest.Imp[i]
   168  
   169  		var bidderExt adapters.ExtImpBidder
   170  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   171  			errs = append(errs, err)
   172  			continue
   173  		}
   174  		if err := json.Unmarshal(bidderExt.Bidder, &taboolaExt); err != nil {
   175  			errs = append(errs, err)
   176  			continue
   177  		}
   178  
   179  		tagId := taboolaExt.TagID
   180  		if len(taboolaExt.TagID) < 1 {
   181  			tagId = taboolaExt.TagId
   182  		}
   183  
   184  		imp.TagID = tagId
   185  		modifiedRequest.Imp[i] = imp
   186  
   187  		if taboolaExt.BidFloor != 0 {
   188  			imp.BidFloor = taboolaExt.BidFloor
   189  			modifiedRequest.Imp[i] = imp
   190  		}
   191  
   192  		if modifiedRequest.Imp[i].Banner != nil {
   193  			if taboolaExt.Position != nil {
   194  				bannerCopy := *imp.Banner
   195  				bannerCopy.Pos = adcom1.PlacementPosition(*taboolaExt.Position).Ptr()
   196  				imp.Banner = &bannerCopy
   197  				modifiedRequest.Imp[i] = imp
   198  			}
   199  			bannerImp = append(bannerImp, modifiedRequest.Imp[i])
   200  		} else if modifiedRequest.Imp[i].Native != nil {
   201  			nativeImp = append(nativeImp, modifiedRequest.Imp[i])
   202  		}
   203  
   204  	}
   205  
   206  	publisher := &openrtb2.Publisher{
   207  		ID: taboolaExt.PublisherId,
   208  	}
   209  
   210  	if modifiedRequest.Site == nil {
   211  		newSite := &openrtb2.Site{
   212  			ID:        taboolaExt.PublisherId,
   213  			Name:      taboolaExt.PublisherId,
   214  			Domain:    evaluateDomain(taboolaExt.PublisherDomain, request),
   215  			Publisher: publisher,
   216  		}
   217  		modifiedRequest.Site = newSite
   218  	} else {
   219  		modifiedSite := *modifiedRequest.Site
   220  		modifiedSite.Publisher = publisher
   221  		modifiedSite.ID = taboolaExt.PublisherId
   222  		modifiedSite.Name = taboolaExt.PublisherId
   223  		modifiedSite.Domain = evaluateDomain(taboolaExt.PublisherDomain, request)
   224  		modifiedRequest.Site = &modifiedSite
   225  	}
   226  
   227  	if taboolaExt.BCat != nil {
   228  		modifiedRequest.BCat = taboolaExt.BCat
   229  	}
   230  
   231  	if taboolaExt.BAdv != nil {
   232  		modifiedRequest.BAdv = taboolaExt.BAdv
   233  	}
   234  
   235  	if taboolaExt.PageType != "" {
   236  		requestExt, requestExtErr := makeRequestExt(taboolaExt.PageType)
   237  		if requestExtErr == nil {
   238  			modifiedRequest.Ext = requestExt
   239  		} else {
   240  			errs = append(errs, requestExtErr)
   241  		}
   242  	}
   243  
   244  	taboolaRequests = append(taboolaRequests, overrideBidRequestImp(&modifiedRequest, nativeImp))
   245  	taboolaRequests = append(taboolaRequests, overrideBidRequestImp(&modifiedRequest, bannerImp))
   246  
   247  	return taboolaRequests, errs
   248  }
   249  
   250  func makeRequestExt(pageType string) (json.RawMessage, error) {
   251  	requestExt := &RequestExt{
   252  		PageType: pageType,
   253  	}
   254  
   255  	requestExtJson, err := json.Marshal(requestExt)
   256  	if err != nil {
   257  		return nil, fmt.Errorf("could not marshal %s, err: %s", requestExt, err)
   258  	}
   259  	return requestExtJson, nil
   260  
   261  }
   262  
   263  func getMediaType(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
   264  	for _, imp := range imps {
   265  		if imp.ID == impID {
   266  			if imp.Banner != nil {
   267  				return openrtb_ext.BidTypeBanner, nil
   268  			} else if imp.Native != nil {
   269  				return openrtb_ext.BidTypeNative, nil
   270  			}
   271  		}
   272  	}
   273  
   274  	return "", &errortypes.BadInput{
   275  		Message: fmt.Sprintf("Failed to find banner/native impression \"%s\" ", impID),
   276  	}
   277  }
   278  
   279  func evaluateDomain(publisherDomain string, request *openrtb2.BidRequest) (result string) {
   280  	if publisherDomain != "" {
   281  		return publisherDomain
   282  	}
   283  	if request.Site != nil {
   284  		return request.Site.Domain
   285  	}
   286  	return ""
   287  }
   288  
   289  func overrideBidRequestImp(originBidRequest *openrtb2.BidRequest, imp []openrtb2.Imp) (bidRequest *openrtb2.BidRequest) {
   290  	bidRequestResult := *originBidRequest
   291  	bidRequestResult.Imp = imp
   292  	return &bidRequestResult
   293  }
   294  
   295  func resolveMacros(bid *openrtb2.Bid) {
   296  	if bid != nil {
   297  		price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
   298  		bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
   299  		bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
   300  	}
   301  }