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

     1  package dmx
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/prebid/openrtb/v20/adcom1"
    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/openrtb_ext"
    17  )
    18  
    19  type DmxAdapter struct {
    20  	endpoint string
    21  }
    22  
    23  // Builder builds a new instance of the DistrictM DMX adapter for the given bidder with the given config.
    24  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    25  	bidder := &DmxAdapter{
    26  		endpoint: config.Endpoint,
    27  	}
    28  	return bidder, nil
    29  }
    30  
    31  type dmxExt struct {
    32  	Bidder dmxParams `json:"bidder"`
    33  }
    34  
    35  type dmxPubExt struct {
    36  	Dmx dmxPubExtId `json:"dmx,omitempty"`
    37  }
    38  
    39  type dmxPubExtId struct {
    40  	Id string `json:"id,omitempty"`
    41  }
    42  
    43  type dmxParams struct {
    44  	TagId       string  `json:"tagid,omitempty"`
    45  	DmxId       string  `json:"dmxid,omitempty"`
    46  	MemberId    string  `json:"memberid,omitempty"`
    47  	PublisherId string  `json:"publisher_id,omitempty"`
    48  	SellerId    string  `json:"seller_id,omitempty"`
    49  	Bidfloor    float64 `json:"bidfloor,omitempty"`
    50  }
    51  
    52  var protocols = []adcom1.MediaCreativeSubtype{2, 3, 5, 6, 7, 8}
    53  
    54  func UserSellerOrPubId(str1, str2 string) string {
    55  	if str1 != "" {
    56  		return str1
    57  	}
    58  	return str2
    59  }
    60  
    61  func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) {
    62  	var imps []openrtb2.Imp
    63  	var rootExtInfo dmxExt
    64  	var publisherId string
    65  	var sellerId string
    66  	var userExt openrtb_ext.ExtUser
    67  	var reqCopy openrtb2.BidRequest = *request
    68  	var dmxReq *openrtb2.BidRequest = &reqCopy
    69  	var dmxRawPubId dmxPubExt
    70  
    71  	if request.User == nil {
    72  		if request.App == nil {
    73  			return nil, []error{errors.New("No user id or app id found. Could not send request to DMX.")}
    74  		}
    75  	}
    76  
    77  	if len(request.Imp) >= 1 {
    78  		err := json.Unmarshal(request.Imp[0].Ext, &rootExtInfo)
    79  		if err != nil {
    80  			errs = append(errs, err)
    81  		} else {
    82  			publisherId = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
    83  			sellerId = rootExtInfo.Bidder.SellerId
    84  		}
    85  	}
    86  
    87  	hasNoID := true
    88  	if request.App != nil {
    89  		appCopy := *request.App
    90  		appPublisherCopy := *request.App.Publisher
    91  		dmxReq.App = &appCopy
    92  		dmxReq.App.Publisher = &appPublisherCopy
    93  		if dmxReq.App.Publisher.ID == "" {
    94  			dmxReq.App.Publisher.ID = publisherId
    95  		}
    96  		dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
    97  		ext, err := json.Marshal(dmxRawPubId)
    98  		if err != nil {
    99  			errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err))
   100  			return nil, errs
   101  		}
   102  		dmxReq.App.Publisher.Ext = ext
   103  		if dmxReq.App.ID != "" {
   104  			hasNoID = false
   105  		}
   106  		if hasNoID {
   107  			if idfa, valid := getIdfa(request); valid {
   108  				dmxReq.App.ID = idfa
   109  				hasNoID = false
   110  			}
   111  		}
   112  	} else {
   113  		dmxReq.App = nil
   114  	}
   115  
   116  	if request.Site != nil {
   117  		siteCopy := *request.Site
   118  		sitePublisherCopy := *request.Site.Publisher
   119  		dmxReq.Site = &siteCopy
   120  		dmxReq.Site.Publisher = &sitePublisherCopy
   121  		if dmxReq.Site.Publisher != nil {
   122  			if dmxReq.Site.Publisher.ID == "" {
   123  				dmxReq.Site.Publisher.ID = publisherId
   124  			}
   125  			dmxRawPubId.Dmx.Id = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
   126  			ext, err := json.Marshal(dmxRawPubId)
   127  			if err != nil {
   128  				errs = append(errs, fmt.Errorf("unable to marshal ext, %v", err))
   129  				return nil, errs
   130  			}
   131  			dmxReq.Site.Publisher.Ext = ext
   132  		} else {
   133  			dmxReq.Site.Publisher = &openrtb2.Publisher{ID: publisherId}
   134  		}
   135  	} else {
   136  		dmxReq.Site = nil
   137  	}
   138  
   139  	if request.User != nil {
   140  		userCopy := *request.User
   141  		dmxReq.User = &userCopy
   142  	} else {
   143  		dmxReq.User = nil
   144  	}
   145  
   146  	if dmxReq.User != nil {
   147  		if dmxReq.User.ID != "" {
   148  			hasNoID = false
   149  		}
   150  		if dmxReq.User.Ext != nil {
   151  			if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil {
   152  				if len(userExt.Eids) > 0 {
   153  					hasNoID = false
   154  				}
   155  			}
   156  		}
   157  	}
   158  
   159  	for _, inst := range dmxReq.Imp {
   160  		var ins openrtb2.Imp
   161  		var params dmxExt
   162  		const intVal int8 = 1
   163  		source := (*json.RawMessage)(&inst.Ext)
   164  		if err := json.Unmarshal(*source, &params); err != nil {
   165  			errs = append(errs, err)
   166  		}
   167  		if isDmxParams(params.Bidder) {
   168  			if inst.Banner != nil {
   169  				if len(inst.Banner.Format) != 0 {
   170  					bannerCopy := *inst.Banner
   171  					if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
   172  						imps = fetchParams(params, inst, ins, imps, &bannerCopy, nil, intVal)
   173  					} else {
   174  						return nil, []error{errors.New("Missing Params for auction to be send")}
   175  					}
   176  				}
   177  			}
   178  
   179  			if inst.Video != nil {
   180  				videoCopy := *inst.Video
   181  				if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
   182  					imps = fetchParams(params, inst, ins, imps, nil, &videoCopy, intVal)
   183  				} else {
   184  					return nil, []error{errors.New("Missing Params for auction to be send")}
   185  				}
   186  			}
   187  		}
   188  
   189  	}
   190  
   191  	dmxReq.Imp = imps
   192  
   193  	if hasNoID {
   194  		return nil, []error{errors.New("This request contained no identifier")}
   195  	}
   196  
   197  	oJson, err := json.Marshal(dmxReq)
   198  
   199  	if err != nil {
   200  		errs = append(errs, err)
   201  		return nil, errs
   202  	}
   203  
   204  	headers := http.Header{}
   205  	headers.Add("Content-Type", "application/json;charset=utf-8")
   206  	reqBidder := &adapters.RequestData{
   207  		Method:  "POST",
   208  		Uri:     adapter.endpoint + addParams(sellerId), //adapter.endpoint,
   209  		Body:    oJson,
   210  		Headers: headers,
   211  		ImpIDs:  openrtb_ext.GetImpIDs(dmxReq.Imp),
   212  	}
   213  
   214  	reqsBidder = append(reqsBidder, reqBidder)
   215  	return
   216  }
   217  
   218  func (adapter *DmxAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   219  	var errs []error
   220  
   221  	if http.StatusNoContent == response.StatusCode {
   222  		return nil, nil
   223  	}
   224  
   225  	if http.StatusBadRequest == response.StatusCode {
   226  		return nil, []error{&errortypes.BadInput{
   227  			Message: fmt.Sprintf("Unexpected status code 400"),
   228  		}}
   229  	}
   230  
   231  	if http.StatusOK != response.StatusCode {
   232  		return nil, []error{&errortypes.BadInput{
   233  			Message: fmt.Sprintf("Unexpected response no status code"),
   234  		}}
   235  	}
   236  
   237  	var bidResp openrtb2.BidResponse
   238  
   239  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   240  		return nil, []error{err}
   241  	}
   242  
   243  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   244  
   245  	for _, sb := range bidResp.SeatBid {
   246  		for i := range sb.Bid {
   247  			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, request.Imp)
   248  			if err != nil {
   249  				errs = append(errs, err)
   250  			} else {
   251  				b := &adapters.TypedBid{
   252  					Bid:     &sb.Bid[i],
   253  					BidType: bidType,
   254  				}
   255  				if b.BidType == openrtb_ext.BidTypeVideo {
   256  					b.Bid.AdM = videoImpInsertion(b.Bid)
   257  				}
   258  				bidResponse.Bids = append(bidResponse.Bids, b)
   259  			}
   260  		}
   261  	}
   262  	return bidResponse, errs
   263  
   264  }
   265  
   266  func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []openrtb2.Imp, banner *openrtb2.Banner, video *openrtb2.Video, intVal int8) []openrtb2.Imp {
   267  	tempImp := inst
   268  	if params.Bidder.Bidfloor != 0 {
   269  		tempImp.BidFloor = params.Bidder.Bidfloor
   270  	}
   271  	if params.Bidder.TagId != "" {
   272  		tempImp.TagID = params.Bidder.TagId
   273  		tempImp.Secure = &intVal
   274  	}
   275  
   276  	if params.Bidder.DmxId != "" {
   277  		tempImp.TagID = params.Bidder.DmxId
   278  		tempImp.Secure = &intVal
   279  	}
   280  	if banner != nil {
   281  		if banner.H == nil || banner.W == nil {
   282  			banner.H = &banner.Format[0].H
   283  			banner.W = &banner.Format[0].W
   284  		}
   285  		tempImp.Banner = banner
   286  	}
   287  
   288  	if video != nil {
   289  		video.Protocols = checkProtocols(video)
   290  		tempImp.Video = video
   291  	}
   292  
   293  	if tempImp.TagID == "" {
   294  		return imps
   295  	}
   296  	imps = append(imps, tempImp)
   297  	return imps
   298  }
   299  
   300  func addParams(str string) string {
   301  	if str != "" {
   302  		return "?sellerid=" + url.QueryEscape(str)
   303  	}
   304  	return ""
   305  }
   306  
   307  func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
   308  	mediaType := openrtb_ext.BidTypeBanner
   309  	for _, imp := range imps {
   310  		if imp.ID == impID {
   311  			if imp.Banner == nil && imp.Video != nil {
   312  				mediaType = openrtb_ext.BidTypeVideo
   313  			}
   314  			return mediaType, nil
   315  		}
   316  	}
   317  
   318  	// This shouldnt happen. Lets handle it just incase by returning an error.
   319  	return "", &errortypes.BadInput{
   320  		Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID),
   321  	}
   322  }
   323  
   324  func videoImpInsertion(bid *openrtb2.Bid) string {
   325  	adm := bid.AdM
   326  	nurl := bid.NURL
   327  	search := "</Impression>"
   328  	imp := "</Impression><Impression><![CDATA[%s]]></Impression>"
   329  	wrapped_nurl := fmt.Sprintf(imp, nurl)
   330  	results := strings.Replace(adm, search, wrapped_nurl, 1)
   331  	return results
   332  }
   333  
   334  func isDmxParams(t interface{}) bool {
   335  	switch t.(type) {
   336  	case dmxParams:
   337  		return true
   338  	default:
   339  		return false
   340  	}
   341  }
   342  
   343  func getIdfa(request *openrtb2.BidRequest) (string, bool) {
   344  	if request.Device == nil {
   345  		return "", false
   346  	}
   347  
   348  	device := request.Device
   349  
   350  	if device.IFA != "" {
   351  		return device.IFA, true
   352  	}
   353  	return "", false
   354  }
   355  func checkProtocols(imp *openrtb2.Video) []adcom1.MediaCreativeSubtype {
   356  	if len(imp.Protocols) > 0 {
   357  		return imp.Protocols
   358  	}
   359  	return protocols
   360  }