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

     1  package flipp
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  
    12  	"github.com/buger/jsonparser"
    13  	"github.com/prebid/go-gdpr/consentconstants"
    14  	"github.com/prebid/go-gdpr/vendorconsent"
    15  	"github.com/prebid/openrtb/v20/openrtb2"
    16  	"github.com/prebid/prebid-server/v2/adapters"
    17  	"github.com/prebid/prebid-server/v2/config"
    18  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    19  	"github.com/prebid/prebid-server/v2/util/uuidutil"
    20  )
    21  
    22  const (
    23  	bannerType      = "banner"
    24  	inlineDivName   = "inline"
    25  	flippBidder     = "flipp"
    26  	defaultCurrency = "USD"
    27  )
    28  
    29  var (
    30  	count    int64 = 1
    31  	adTypes        = []int64{4309, 641}
    32  	dtxTypes       = []int64{5061}
    33  )
    34  
    35  type adapter struct {
    36  	endpoint      string
    37  	uuidGenerator uuidutil.UUIDGenerator
    38  }
    39  
    40  var (
    41  	errRequestEmpty = errors.New("adapterRequest is empty")
    42  )
    43  
    44  // Builder builds a new instance of the Flipp adapter for the given bidder with the given config.
    45  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    46  	bidder := &adapter{
    47  		endpoint:      config.Endpoint,
    48  		uuidGenerator: uuidutil.UUIDRandomGenerator{},
    49  	}
    50  	return bidder, nil
    51  }
    52  
    53  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    54  	adapterRequests := make([]*adapters.RequestData, 0, len(request.Imp))
    55  	var errors []error
    56  
    57  	for _, imp := range request.Imp {
    58  		adapterReq, err := a.processImp(request, imp)
    59  		if err != nil {
    60  			errors = append(errors, err)
    61  			continue
    62  		}
    63  		adapterRequests = append(adapterRequests, adapterReq)
    64  	}
    65  	if len(adapterRequests) == 0 {
    66  		return nil, append(errors, errRequestEmpty)
    67  	}
    68  	return adapterRequests, errors
    69  }
    70  
    71  func (a *adapter) makeRequest(request *openrtb2.BidRequest, campaignRequestBody CampaignRequestBody, impID string) (*adapters.RequestData, error) {
    72  	campaignRequestBodyJSON, err := json.Marshal(campaignRequestBody)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	headers := http.Header{}
    78  	headers.Add("Content-Type", "application/json")
    79  	if request.Device != nil && request.Device.UA != "" {
    80  		headers.Add("User-Agent", request.Device.UA)
    81  	}
    82  	return &adapters.RequestData{
    83  		Method:  http.MethodPost,
    84  		Uri:     a.endpoint,
    85  		Body:    campaignRequestBodyJSON,
    86  		Headers: headers,
    87  		ImpIDs:  []string{impID},
    88  	}, err
    89  }
    90  
    91  func (a *adapter) processImp(request *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) {
    92  	var flippExtParams openrtb_ext.ImpExtFlipp
    93  	params, _, _, err := jsonparser.Get(imp.Ext, "bidder")
    94  	if err != nil {
    95  		return nil, fmt.Errorf("flipp params not found. %v", err)
    96  	}
    97  	err = json.Unmarshal(params, &flippExtParams)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("unable to extract flipp params. %v", err)
   100  	}
   101  
   102  	publisherUrl, err := url.Parse(request.Site.Page)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("unable to parse site url. %v", err)
   105  	}
   106  
   107  	var contentCode string
   108  	if flippExtParams.Options.ContentCode != "" {
   109  		contentCode = flippExtParams.Options.ContentCode
   110  	} else if publisherUrl != nil {
   111  		contentCode = publisherUrl.Query().Get("flipp-content-code")
   112  	}
   113  
   114  	placement := Placement{
   115  		DivName: inlineDivName,
   116  		SiteID:  &flippExtParams.SiteID,
   117  		AdTypes: getAdTypes(flippExtParams.CreativeType),
   118  		ZoneIds: flippExtParams.ZoneIds,
   119  		Count:   &count,
   120  		Prebid:  buildPrebidRequest(flippExtParams, request, imp),
   121  		Properties: &Properties{
   122  			ContentCode: &contentCode,
   123  		},
   124  		Options: flippExtParams.Options,
   125  	}
   126  
   127  	var userIP string
   128  	if request.Device != nil && request.Device.IP != "" {
   129  		userIP = request.Device.IP
   130  	} else {
   131  		return nil, fmt.Errorf("no IP set in flipp bidder params or request device")
   132  	}
   133  
   134  	var userKey string
   135  	if request.User != nil && request.User.ID != "" {
   136  		userKey = request.User.ID
   137  	} else if flippExtParams.UserKey != "" && paramsUserKeyPermitted(request) {
   138  		userKey = flippExtParams.UserKey
   139  	} else {
   140  		uid, err := a.uuidGenerator.Generate()
   141  		if err != nil {
   142  			return nil, fmt.Errorf("unable to generate user uuid. %v", err)
   143  		}
   144  		userKey = uid
   145  	}
   146  
   147  	keywordsArray := strings.Split(request.Site.Keywords, ",")
   148  
   149  	campaignRequestBody := CampaignRequestBody{
   150  		Placements: []*Placement{&placement},
   151  		URL:        request.Site.Page,
   152  		Keywords:   keywordsArray,
   153  		IP:         userIP,
   154  		User: &CampaignRequestBodyUser{
   155  			Key: &userKey,
   156  		},
   157  	}
   158  
   159  	adapterReq, err := a.makeRequest(request, campaignRequestBody, imp.ID)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("make request failed with err %v", err)
   162  	}
   163  
   164  	return adapterReq, nil
   165  }
   166  
   167  func buildPrebidRequest(flippExtParams openrtb_ext.ImpExtFlipp, request *openrtb2.BidRequest, imp openrtb2.Imp) *PrebidRequest {
   168  	var height int64
   169  	var width int64
   170  	if imp.Banner != nil && len(imp.Banner.Format) > 0 {
   171  		height = imp.Banner.Format[0].H
   172  		width = imp.Banner.Format[0].W
   173  	}
   174  	prebidRequest := PrebidRequest{
   175  		CreativeType:            &flippExtParams.CreativeType,
   176  		PublisherNameIdentifier: &flippExtParams.PublisherNameIdentifier,
   177  		RequestID:               &imp.ID,
   178  		Height:                  &height,
   179  		Width:                   &width,
   180  	}
   181  	return &prebidRequest
   182  }
   183  
   184  func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   185  	if adapters.IsResponseStatusCodeNoContent(responseData) {
   186  		return nil, nil
   187  	}
   188  
   189  	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
   190  		return nil, []error{err}
   191  	}
   192  
   193  	var campaignResponseBody CampaignResponseBody
   194  	if err := json.Unmarshal(responseData.Body, &campaignResponseBody); err != nil {
   195  		return nil, []error{err}
   196  	}
   197  
   198  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
   199  	bidResponse.Currency = defaultCurrency
   200  	for _, imp := range request.Imp {
   201  		for _, decision := range campaignResponseBody.Decisions.Inline {
   202  			if *decision.Prebid.RequestID == imp.ID {
   203  				b := &adapters.TypedBid{
   204  					Bid:     buildBid(decision, imp.ID),
   205  					BidType: openrtb_ext.BidType(bannerType),
   206  				}
   207  				bidResponse.Bids = append(bidResponse.Bids, b)
   208  			}
   209  		}
   210  	}
   211  	return bidResponse, nil
   212  }
   213  
   214  func getAdTypes(creativeType string) []int64 {
   215  	if creativeType == "DTX" {
   216  		return dtxTypes
   217  	}
   218  	return adTypes
   219  }
   220  
   221  func buildBid(decision *InlineModel, impId string) *openrtb2.Bid {
   222  	bid := &openrtb2.Bid{
   223  		CrID:  fmt.Sprint(decision.CreativeID),
   224  		Price: *decision.Prebid.Cpm,
   225  		AdM:   *decision.Prebid.Creative,
   226  		ID:    fmt.Sprint(decision.AdID),
   227  		ImpID: impId,
   228  	}
   229  	if len(decision.Contents) > 0 || decision.Contents[0] != nil || decision.Contents[0].Data != nil {
   230  		if decision.Contents[0].Data.Width != 0 {
   231  			bid.W = decision.Contents[0].Data.Width
   232  		}
   233  		bid.H = 0
   234  	}
   235  	return bid
   236  }
   237  
   238  func paramsUserKeyPermitted(request *openrtb2.BidRequest) bool {
   239  	if request.Regs != nil {
   240  		if request.Regs.COPPA == 1 {
   241  			return false
   242  		}
   243  		if request.Regs.GDPR != nil && *request.Regs.GDPR == 1 {
   244  			return false
   245  		}
   246  	}
   247  	if request.Ext != nil {
   248  		var extData struct {
   249  			TransmitEids *bool `json:"transmitEids,omitempty"`
   250  		}
   251  		if err := json.Unmarshal(request.Ext, &extData); err == nil {
   252  			if extData.TransmitEids != nil && !*extData.TransmitEids {
   253  				return false
   254  			}
   255  		}
   256  	}
   257  	if request.User != nil && request.User.Consent != "" {
   258  		data, err := base64.RawURLEncoding.DecodeString(request.User.Consent)
   259  		if err != nil {
   260  			return true
   261  		}
   262  		consent, err := vendorconsent.Parse(data)
   263  		if err != nil {
   264  			return true
   265  		}
   266  		if !consent.PurposeAllowed(consentconstants.ContentSelectionDeliveryReporting) {
   267  			return false
   268  		}
   269  	}
   270  	return true
   271  }