github.com/prebid/prebid-server@v0.275.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/v19/adcom1"
    12  	"github.com/prebid/openrtb/v19/openrtb2"
    13  	"github.com/prebid/prebid-server/adapters"
    14  	"github.com/prebid/prebid-server/config"
    15  	"github.com/prebid/prebid-server/errortypes"
    16  	"github.com/prebid/prebid-server/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 anyHasId = false
    68  	var reqCopy openrtb2.BidRequest = *request
    69  	var dmxReq *openrtb2.BidRequest = &reqCopy
    70  	var dmxRawPubId dmxPubExt
    71  
    72  	if request.User == nil {
    73  		if request.App == nil {
    74  			return nil, []error{errors.New("No user id or app id found. Could not send request to DMX.")}
    75  		}
    76  	}
    77  
    78  	if len(request.Imp) >= 1 {
    79  		err := json.Unmarshal(request.Imp[0].Ext, &rootExtInfo)
    80  		if err != nil {
    81  			errs = append(errs, err)
    82  		} else {
    83  			publisherId = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
    84  			sellerId = rootExtInfo.Bidder.SellerId
    85  		}
    86  	}
    87  
    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  			anyHasId = true
   105  		}
   106  		if anyHasId == false {
   107  			if idfa, valid := getIdfa(request); valid {
   108  				dmxReq.App.ID = idfa
   109  				anyHasId = true
   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  			anyHasId = true
   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  					anyHasId = true
   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 anyHasId == false {
   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  	}
   212  
   213  	reqsBidder = append(reqsBidder, reqBidder)
   214  	return
   215  }
   216  
   217  func (adapter *DmxAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   218  	var errs []error
   219  
   220  	if http.StatusNoContent == response.StatusCode {
   221  		return nil, nil
   222  	}
   223  
   224  	if http.StatusBadRequest == response.StatusCode {
   225  		return nil, []error{&errortypes.BadInput{
   226  			Message: fmt.Sprintf("Unexpected status code 400"),
   227  		}}
   228  	}
   229  
   230  	if http.StatusOK != response.StatusCode {
   231  		return nil, []error{&errortypes.BadInput{
   232  			Message: fmt.Sprintf("Unexpected response no status code"),
   233  		}}
   234  	}
   235  
   236  	var bidResp openrtb2.BidResponse
   237  
   238  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   239  		return nil, []error{err}
   240  	}
   241  
   242  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   243  
   244  	for _, sb := range bidResp.SeatBid {
   245  		for i := range sb.Bid {
   246  			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, request.Imp)
   247  			if err != nil {
   248  				errs = append(errs, err)
   249  			} else {
   250  				b := &adapters.TypedBid{
   251  					Bid:     &sb.Bid[i],
   252  					BidType: bidType,
   253  				}
   254  				if b.BidType == openrtb_ext.BidTypeVideo {
   255  					b.Bid.AdM = videoImpInsertion(b.Bid)
   256  				}
   257  				bidResponse.Bids = append(bidResponse.Bids, b)
   258  			}
   259  		}
   260  	}
   261  	return bidResponse, errs
   262  
   263  }
   264  
   265  func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []openrtb2.Imp, banner *openrtb2.Banner, video *openrtb2.Video, intVal int8) []openrtb2.Imp {
   266  	var tempimp 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  }