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

     1  package outbrain
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/prebid/openrtb/v20/native1"
     9  	nativeResponse "github.com/prebid/openrtb/v20/native1/response"
    10  	"github.com/prebid/openrtb/v20/openrtb2"
    11  	"github.com/prebid/prebid-server/v2/adapters"
    12  	"github.com/prebid/prebid-server/v2/config"
    13  	"github.com/prebid/prebid-server/v2/errortypes"
    14  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    15  )
    16  
    17  type adapter struct {
    18  	endpoint string
    19  }
    20  
    21  // Builder builds a new instance of the Outbrain adapter for the given bidder with the given config.
    22  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    23  	bidder := &adapter{
    24  		endpoint: config.Endpoint,
    25  	}
    26  	return bidder, nil
    27  }
    28  
    29  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    30  	reqCopy := *request
    31  
    32  	var errs []error
    33  	var outbrainExt openrtb_ext.ExtImpOutbrain
    34  	for i := 0; i < len(reqCopy.Imp); i++ {
    35  		imp := reqCopy.Imp[i]
    36  
    37  		var bidderExt adapters.ExtImpBidder
    38  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    39  			errs = append(errs, err)
    40  			continue
    41  		}
    42  		if err := json.Unmarshal(bidderExt.Bidder, &outbrainExt); err != nil {
    43  			errs = append(errs, err)
    44  			continue
    45  		}
    46  		if outbrainExt.TagId != "" {
    47  			imp.TagID = outbrainExt.TagId
    48  			reqCopy.Imp[i] = imp
    49  		}
    50  	}
    51  
    52  	publisher := &openrtb2.Publisher{
    53  		ID:     outbrainExt.Publisher.Id,
    54  		Name:   outbrainExt.Publisher.Name,
    55  		Domain: outbrainExt.Publisher.Domain,
    56  	}
    57  	if reqCopy.Site != nil {
    58  		siteCopy := *reqCopy.Site
    59  		siteCopy.Publisher = publisher
    60  		reqCopy.Site = &siteCopy
    61  	} else if reqCopy.App != nil {
    62  		appCopy := *reqCopy.App
    63  		appCopy.Publisher = publisher
    64  		reqCopy.App = &appCopy
    65  	}
    66  
    67  	if outbrainExt.BCat != nil {
    68  		reqCopy.BCat = outbrainExt.BCat
    69  	}
    70  	if outbrainExt.BAdv != nil {
    71  		reqCopy.BAdv = outbrainExt.BAdv
    72  	}
    73  
    74  	requestJSON, err := json.Marshal(reqCopy)
    75  	if err != nil {
    76  		errs = append(errs, err)
    77  		return nil, errs
    78  	}
    79  
    80  	requestData := &adapters.RequestData{
    81  		Method: "POST",
    82  		Uri:    a.endpoint,
    83  		Body:   requestJSON,
    84  		ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp),
    85  	}
    86  
    87  	return []*adapters.RequestData{requestData}, nil
    88  }
    89  
    90  func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
    91  	if responseData.StatusCode == http.StatusNoContent {
    92  		return nil, nil
    93  	}
    94  
    95  	if responseData.StatusCode == http.StatusBadRequest {
    96  		err := &errortypes.BadInput{
    97  			Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
    98  		}
    99  		return nil, []error{err}
   100  	}
   101  
   102  	if responseData.StatusCode != http.StatusOK {
   103  		err := &errortypes.BadServerResponse{
   104  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
   105  		}
   106  		return nil, []error{err}
   107  	}
   108  
   109  	var response openrtb2.BidResponse
   110  	if err := json.Unmarshal(responseData.Body, &response); err != nil {
   111  		return nil, []error{err}
   112  	}
   113  
   114  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
   115  	bidResponse.Currency = response.Cur
   116  
   117  	var errs []error
   118  	for _, seatBid := range response.SeatBid {
   119  		for i := range seatBid.Bid {
   120  			bid := seatBid.Bid[i]
   121  			bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp)
   122  			if err != nil {
   123  				errs = append(errs, err)
   124  				continue
   125  			}
   126  			if bidType == openrtb_ext.BidTypeNative {
   127  				var nativePayload nativeResponse.Response
   128  				if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativePayload); err != nil {
   129  					errs = append(errs, err)
   130  					continue
   131  				}
   132  				transformEventTrackers(&nativePayload)
   133  				nativePayloadJson, err := json.Marshal(nativePayload)
   134  				if err != nil {
   135  					errs = append(errs, err)
   136  					continue
   137  				}
   138  				bid.AdM = string(nativePayloadJson)
   139  			}
   140  
   141  			b := &adapters.TypedBid{
   142  				Bid:     &bid,
   143  				BidType: bidType,
   144  			}
   145  			bidResponse.Bids = append(bidResponse.Bids, b)
   146  		}
   147  	}
   148  
   149  	return bidResponse, errs
   150  }
   151  
   152  func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
   153  	for _, imp := range imps {
   154  		if imp.ID == impID {
   155  			if imp.Native != nil {
   156  				return openrtb_ext.BidTypeNative, nil
   157  			} else if imp.Banner != nil {
   158  				return openrtb_ext.BidTypeBanner, nil
   159  			} else if imp.Video != nil {
   160  				return openrtb_ext.BidTypeVideo, nil
   161  			}
   162  		}
   163  	}
   164  
   165  	return "", &errortypes.BadInput{
   166  		Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID),
   167  	}
   168  }
   169  
   170  func transformEventTrackers(nativePayload *nativeResponse.Response) {
   171  	// the native-trk.js library used to trigger the trackers currently doesn't support the native 1.2 eventtrackers,
   172  	// so transform them to the deprecated imptrackers and jstracker
   173  	for _, eventTracker := range nativePayload.EventTrackers {
   174  		if eventTracker.Event != native1.EventTypeImpression {
   175  			continue
   176  		}
   177  		switch eventTracker.Method {
   178  		case native1.EventTrackingMethodImage:
   179  			nativePayload.ImpTrackers = append(nativePayload.ImpTrackers, eventTracker.URL)
   180  		case native1.EventTrackingMethodJS:
   181  			nativePayload.JSTracker = fmt.Sprintf("<script src=\"%s\"></script>", eventTracker.URL)
   182  		}
   183  	}
   184  	nativePayload.EventTrackers = nil
   185  }