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

     1  package invibes
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/prebid/openrtb/v20/openrtb2"
    13  	"github.com/prebid/prebid-server/v2/adapters"
    14  	"github.com/prebid/prebid-server/v2/config"
    15  	"github.com/prebid/prebid-server/v2/errortypes"
    16  	"github.com/prebid/prebid-server/v2/macros"
    17  	"github.com/prebid/prebid-server/v2/metrics"
    18  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    19  )
    20  
    21  const adapterVersion = "prebid_1.0.0"
    22  const invibesBidVersion = "4"
    23  
    24  type InvibesAdRequest struct {
    25  	BidParamsJson string `json:"BidParamsJson"`
    26  	Location      string `json:"Location"`
    27  	Lid           string `json:"Lid"`
    28  	IsTestBid     bool   `json:"IsTestBid"`
    29  	Kw            string `json:"Kw"`
    30  	IsAMP         bool   `json:"IsAmp"`
    31  	Width         string `json:"Width"`
    32  	Height        string `json:"Height"`
    33  	GDPRConsent   string `json:"GdprConsent"`
    34  	GDPR          bool   `json:"Gdpr"`
    35  	Bvid          string `json:"Bvid"`
    36  	InvibBVLog    bool   `json:"InvibBVLog"`
    37  	VideoAdDebug  bool   `json:"VideoAdDebug"`
    38  }
    39  type InvibesBidParams struct {
    40  	PlacementIDs []string                            `json:"PlacementIds"`
    41  	BidVersion   string                              `json:"BidVersion"`
    42  	Properties   map[string]InvibesPlacementProperty `json:"Properties"`
    43  }
    44  type InvibesPlacementProperty struct {
    45  	Formats []openrtb2.Format `json:"Formats"`
    46  	ImpID   string            `json:"ImpId"`
    47  }
    48  type InvibesInternalParams struct {
    49  	BidParams   InvibesBidParams
    50  	DomainID    int
    51  	IsAMP       bool
    52  	GDPR        bool
    53  	GDPRConsent string
    54  
    55  	TestBvid string
    56  	TestLog  bool
    57  }
    58  type BidServerBidderResponse struct {
    59  	Currency  string              `json:"currency"`
    60  	TypedBids []BidServerTypedBid `json:"typedBids"`
    61  	Error     string              `json:"error"`
    62  }
    63  type BidServerTypedBid struct {
    64  	Bid          openrtb2.Bid `json:"bid"`
    65  	DealPriority int          `json:"dealPriority"`
    66  }
    67  
    68  func (a *InvibesInternalParams) IsTestRequest() bool {
    69  	return a.TestBvid != ""
    70  }
    71  
    72  type InvibesAdapter struct {
    73  	EndpointTemplate *template.Template
    74  }
    75  
    76  // Builder builds a new instance of the Invibes adapter for the given bidder with the given config.
    77  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    78  	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
    79  	if err != nil {
    80  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    81  	}
    82  
    83  	bidder := InvibesAdapter{
    84  		EndpointTemplate: template,
    85  	}
    86  	return &bidder, nil
    87  }
    88  
    89  func (a *InvibesAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    90  	var httpRequests []*adapters.RequestData
    91  	var tempErrors []error
    92  	gdprApplies, consentString := readGDPR(request)
    93  
    94  	var invibesInternalParams InvibesInternalParams = InvibesInternalParams{
    95  		BidParams: InvibesBidParams{
    96  			Properties: make(map[string]InvibesPlacementProperty),
    97  			BidVersion: invibesBidVersion,
    98  		},
    99  	}
   100  
   101  	for _, imp := range request.Imp {
   102  		var bidderExt adapters.ExtImpBidder
   103  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   104  			tempErrors = append(tempErrors, &errortypes.BadInput{
   105  				Message: "Error parsing bidderExt object",
   106  			})
   107  			continue
   108  		}
   109  		var invibesExt openrtb_ext.ExtImpInvibes
   110  		if err := json.Unmarshal(bidderExt.Bidder, &invibesExt); err != nil {
   111  			tempErrors = append(tempErrors, &errortypes.BadInput{
   112  				Message: "Error parsing invibesExt parameters",
   113  			})
   114  			continue
   115  		}
   116  		if imp.Banner == nil {
   117  			tempErrors = append(tempErrors, &errortypes.BadInput{
   118  				Message: "Banner not specified",
   119  			})
   120  			continue
   121  		}
   122  
   123  		adFormats := readAdFormats(*imp.Banner)
   124  
   125  		invibesInternalParams.DomainID = invibesExt.DomainID
   126  		invibesInternalParams.BidParams.PlacementIDs = append(invibesInternalParams.BidParams.PlacementIDs, strings.TrimSpace(invibesExt.PlacementID))
   127  		invibesInternalParams.BidParams.Properties[invibesExt.PlacementID] = InvibesPlacementProperty{
   128  			ImpID:   imp.ID,
   129  			Formats: adFormats,
   130  		}
   131  		if invibesExt.Debug.TestBvid != "" {
   132  			invibesInternalParams.TestBvid = invibesExt.Debug.TestBvid
   133  		}
   134  		invibesInternalParams.TestLog = invibesExt.Debug.TestLog
   135  	}
   136  	if reqInfo.PbsEntryPoint == metrics.ReqTypeAMP {
   137  		invibesInternalParams.IsAMP = true
   138  	}
   139  
   140  	if len(invibesInternalParams.BidParams.PlacementIDs) == 0 {
   141  		return nil, tempErrors
   142  	}
   143  
   144  	var finalErrors []error
   145  	invibesInternalParams.GDPR = gdprApplies
   146  	invibesInternalParams.GDPRConsent = consentString
   147  
   148  	newHttpRequest, err := a.makeRequest(invibesInternalParams, reqInfo, httpRequests, request)
   149  	if err != nil {
   150  		finalErrors = append(finalErrors, err)
   151  	} else if newHttpRequest != nil {
   152  		httpRequests = append(httpRequests, newHttpRequest)
   153  	}
   154  
   155  	return httpRequests, finalErrors
   156  }
   157  
   158  func readGDPR(request *openrtb2.BidRequest) (bool, string) {
   159  	consentString := ""
   160  	if request.User != nil {
   161  		var extUser openrtb_ext.ExtUser
   162  		if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
   163  			consentString = extUser.Consent
   164  		}
   165  	}
   166  	gdprApplies := true
   167  	var extRegs openrtb_ext.ExtRegs
   168  	if request.Regs != nil {
   169  		if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil {
   170  			if extRegs.GDPR != nil {
   171  				gdprApplies = (*extRegs.GDPR == 1)
   172  			}
   173  		}
   174  	}
   175  	return gdprApplies, consentString
   176  }
   177  
   178  func readAdFormats(currentBanner openrtb2.Banner) []openrtb2.Format {
   179  	var adFormats []openrtb2.Format
   180  	if currentBanner.Format != nil {
   181  		adFormats = currentBanner.Format
   182  	} else if currentBanner.W != nil && currentBanner.H != nil {
   183  		adFormats = []openrtb2.Format{
   184  			{
   185  				W: *currentBanner.W,
   186  				H: *currentBanner.H,
   187  			},
   188  		}
   189  	}
   190  	return adFormats
   191  }
   192  
   193  func (a *InvibesAdapter) makeRequest(invibesParams InvibesInternalParams, reqInfo *adapters.ExtraRequestInfo, existingRequests []*adapters.RequestData, request *openrtb2.BidRequest) (*adapters.RequestData, error) {
   194  
   195  	url, err := a.makeURL(request, invibesParams.DomainID)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	parameter, errp := a.makeParameter(invibesParams, request)
   200  	if errp != nil {
   201  		return nil, errp
   202  	}
   203  	body, errm := json.Marshal(parameter)
   204  	if errm != nil {
   205  		return nil, errm
   206  	}
   207  
   208  	headers := http.Header{}
   209  	headers.Add("Content-Type", "application/json;charset=utf-8")
   210  	headers.Add("Accept", "application/json")
   211  
   212  	if request.Device != nil {
   213  		headers.Add("User-Agent", request.Device.UA)
   214  	}
   215  
   216  	if request.Device != nil {
   217  		if request.Device.IP != "" {
   218  			headers.Add("X-Forwarded-For", request.Device.IP)
   219  		} else if request.Device.IPv6 != "" {
   220  			headers.Add("X-Forwarded-For", request.Device.IPv6)
   221  		}
   222  	}
   223  	if request.Site != nil {
   224  		headers.Add("Referer", request.Site.Page)
   225  	}
   226  	headers.Add("Aver", adapterVersion)
   227  
   228  	return &adapters.RequestData{
   229  		Method:  "POST",
   230  		Uri:     url,
   231  		Headers: headers,
   232  		Body:    body,
   233  		ImpIDs:  getImpIDs(invibesParams.BidParams.Properties),
   234  	}, nil
   235  }
   236  
   237  func (a *InvibesAdapter) makeParameter(invibesParams InvibesInternalParams, request *openrtb2.BidRequest) (*InvibesAdRequest, error) {
   238  	var lid string = ""
   239  	if request.User != nil && request.User.BuyerUID != "" {
   240  		lid = request.User.BuyerUID
   241  	}
   242  	if request.Site == nil {
   243  		return nil, &errortypes.BadInput{
   244  			Message: "Site not specified",
   245  		}
   246  	}
   247  
   248  	var width, height string
   249  	if request.Device != nil {
   250  		if request.Device.W > 0 {
   251  			width = strconv.FormatInt(request.Device.W, 10)
   252  		}
   253  
   254  		if request.Device.H > 0 {
   255  			height = strconv.FormatInt(request.Device.H, 10)
   256  		}
   257  	}
   258  
   259  	var invRequest InvibesAdRequest
   260  	bidParamsJson, err := json.Marshal(invibesParams.BidParams)
   261  	if err == nil {
   262  		invRequest = InvibesAdRequest{
   263  			IsTestBid:     invibesParams.IsTestRequest(),
   264  			BidParamsJson: string(bidParamsJson),
   265  			Location:      request.Site.Page,
   266  			Lid:           lid,
   267  			Kw:            request.Site.Keywords,
   268  			IsAMP:         invibesParams.IsAMP,
   269  			Width:         width,
   270  			Height:        height,
   271  			GDPRConsent:   invibesParams.GDPRConsent,
   272  			GDPR:          invibesParams.GDPR,
   273  			Bvid:          invibesParams.TestBvid,
   274  			InvibBVLog:    invibesParams.TestLog,
   275  			VideoAdDebug:  invibesParams.TestLog,
   276  		}
   277  	}
   278  
   279  	return &invRequest, err
   280  }
   281  
   282  func (a *InvibesAdapter) makeURL(request *openrtb2.BidRequest, domainID int) (string, error) {
   283  	var subdomain string
   284  	if domainID == 0 || domainID == 1 || domainID == 1001 {
   285  		subdomain = "bid"
   286  	} else if domainID < 1002 {
   287  		subdomain = "bid" + strconv.Itoa(domainID)
   288  	} else {
   289  		subdomain = "bid" + strconv.Itoa(domainID-1000)
   290  	}
   291  
   292  	var endpointURL *url.URL
   293  	endpointParams := macros.EndpointTemplateParams{ZoneID: subdomain}
   294  	host, err := macros.ResolveMacros(a.EndpointTemplate, endpointParams)
   295  
   296  	if err == nil {
   297  		endpointURL, err = url.Parse(host)
   298  	}
   299  	if err != nil {
   300  		return "", &errortypes.BadInput{
   301  			Message: "Unable to parse url template: " + err.Error(),
   302  		}
   303  	}
   304  
   305  	return endpointURL.String(), nil
   306  }
   307  
   308  func (a *InvibesAdapter) MakeBids(
   309  	internalRequest *openrtb2.BidRequest,
   310  	externalRequest *adapters.RequestData,
   311  	response *adapters.ResponseData,
   312  ) (*adapters.BidderResponse, []error) {
   313  	if response.StatusCode != http.StatusOK {
   314  		return nil, []error{fmt.Errorf("Unexpected status code: %d.", response.StatusCode)}
   315  	}
   316  
   317  	bidResponse := BidServerBidderResponse{}
   318  	if err := json.Unmarshal(response.Body, &bidResponse); err != nil {
   319  		return nil, []error{err}
   320  	}
   321  
   322  	var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.TypedBids))
   323  	var errors []error
   324  	parsedResponses.Currency = bidResponse.Currency
   325  
   326  	if bidResponse.Error != "" {
   327  		return nil, []error{fmt.Errorf("Server error: %s.", bidResponse.Error)}
   328  	}
   329  	for _, typedbid := range bidResponse.TypedBids {
   330  		bid := typedbid.Bid
   331  		parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{
   332  			Bid:          &bid,
   333  			BidType:      openrtb_ext.BidTypeBanner,
   334  			DealPriority: typedbid.DealPriority,
   335  		})
   336  	}
   337  
   338  	return parsedResponses, errors
   339  }
   340  
   341  func getImpIDs(bidParamsProperties map[string]InvibesPlacementProperty) []string {
   342  	impIDs := make([]string, 0, len(bidParamsProperties))
   343  	for i := range bidParamsProperties {
   344  		impIDs = append(impIDs, bidParamsProperties[i].ImpID)
   345  	}
   346  	return impIDs
   347  }