github.com/prebid/prebid-server@v0.275.0/adapters/tappx/tappx.go (about)

     1  package tappx
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"regexp"
     9  	"strconv"
    10  	"text/template"
    11  	"time"
    12  
    13  	"github.com/prebid/openrtb/v19/openrtb2"
    14  	"github.com/prebid/prebid-server/adapters"
    15  	"github.com/prebid/prebid-server/config"
    16  	"github.com/prebid/prebid-server/errortypes"
    17  	"github.com/prebid/prebid-server/macros"
    18  	"github.com/prebid/prebid-server/openrtb_ext"
    19  )
    20  
    21  const TAPPX_BIDDER_VERSION = "1.5"
    22  const TYPE_CNN = "prebid"
    23  
    24  type TappxAdapter struct {
    25  	endpointTemplate *template.Template
    26  }
    27  
    28  type Bidder struct {
    29  	Tappxkey string   `json:"tappxkey"`
    30  	Mktag    string   `json:"mktag,omitempty"`
    31  	Bcid     []string `json:"bcid,omitempty"`
    32  	Bcrid    []string `json:"bcrid,omitempty"`
    33  }
    34  
    35  type Ext struct {
    36  	Bidder `json:"bidder"`
    37  }
    38  
    39  // Builder builds a new instance of the Tappx adapter for the given bidder with the given config.
    40  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    41  	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    44  	}
    45  
    46  	bidder := &TappxAdapter{
    47  		endpointTemplate: template,
    48  	}
    49  	return bidder, nil
    50  }
    51  
    52  func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    53  	if len(request.Imp) == 0 {
    54  		return nil, []error{&errortypes.BadInput{
    55  			Message: "No impression in the bid request",
    56  		}}
    57  	}
    58  
    59  	var bidderExt adapters.ExtImpBidder
    60  	if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil {
    61  		return nil, []error{&errortypes.BadInput{
    62  			Message: "Error parsing bidderExt object",
    63  		}}
    64  	}
    65  	var tappxExt openrtb_ext.ExtImpTappx
    66  	if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil {
    67  		return nil, []error{&errortypes.BadInput{
    68  			Message: "Error parsing tappxExt parameters",
    69  		}}
    70  	}
    71  
    72  	ext := Ext{
    73  		Bidder: Bidder{
    74  			Tappxkey: tappxExt.TappxKey,
    75  			Mktag:    tappxExt.Mktag,
    76  			Bcid:     tappxExt.Bcid,
    77  			Bcrid:    tappxExt.Bcrid,
    78  		},
    79  	}
    80  
    81  	if jsonext, err := json.Marshal(ext); err == nil {
    82  		request.Ext = jsonext
    83  	} else {
    84  		return nil, []error{&errortypes.FailedToRequestBids{
    85  			Message: "Error marshaling tappxExt parameters",
    86  		}}
    87  	}
    88  
    89  	var test int
    90  	test = int(request.Test)
    91  
    92  	url, err := a.buildEndpointURL(&tappxExt, test)
    93  	if url == "" {
    94  		return nil, []error{err}
    95  	}
    96  
    97  	if tappxExt.BidFloor > 0 {
    98  		request.Imp[0].BidFloor = tappxExt.BidFloor
    99  	}
   100  
   101  	reqJSON, err := json.Marshal(request)
   102  	if err != nil {
   103  		return nil, []error{&errortypes.BadInput{
   104  			Message: "Error parsing reqJSON object",
   105  		}}
   106  	}
   107  
   108  	headers := http.Header{}
   109  	headers.Add("Content-Type", "application/json;charset=utf-8")
   110  	headers.Add("Accept", "application/json")
   111  	return []*adapters.RequestData{{
   112  		Method:  "POST",
   113  		Uri:     url,
   114  		Body:    reqJSON,
   115  		Headers: headers,
   116  	}}, []error{}
   117  }
   118  
   119  // Builds enpoint url based on adapter-specific pub settings from imp.ext
   120  func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test int) (string, error) {
   121  
   122  	if params.Endpoint == "" {
   123  		return "", &errortypes.BadInput{
   124  			Message: "Tappx endpoint undefined",
   125  		}
   126  	}
   127  
   128  	if params.TappxKey == "" {
   129  		return "", &errortypes.BadInput{
   130  			Message: "Tappx key undefined",
   131  		}
   132  	}
   133  
   134  	tappxHost := "tappx.com"
   135  	isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2,3}|test)$`, []byte(params.Endpoint))
   136  	if isNewEndpoint {
   137  		tappxHost = params.Endpoint + ".pub.tappx.com/rtb/"
   138  	} else {
   139  		tappxHost = "ssp.api.tappx.com/rtb/v2/"
   140  	}
   141  
   142  	endpointParams := macros.EndpointTemplateParams{Host: tappxHost}
   143  	host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams)
   144  
   145  	if err != nil {
   146  		return "", &errortypes.BadInput{
   147  			Message: "Unable to parse endpoint url template: " + err.Error(),
   148  		}
   149  	}
   150  
   151  	thisURI, err := url.Parse(host)
   152  	if err != nil {
   153  		return "", &errortypes.BadInput{
   154  			Message: "Malformed URL: " + err.Error(),
   155  		}
   156  	}
   157  
   158  	if !isNewEndpoint {
   159  		thisURI.Path += params.Endpoint
   160  	}
   161  
   162  	queryParams := url.Values{}
   163  
   164  	queryParams.Add("tappxkey", params.TappxKey)
   165  
   166  	if test == 0 {
   167  		t := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
   168  		queryParams.Add("ts", strconv.Itoa(int(t)))
   169  	}
   170  
   171  	queryParams.Add("v", TAPPX_BIDDER_VERSION)
   172  
   173  	queryParams.Add("type_cnn", TYPE_CNN)
   174  
   175  	thisURI.RawQuery = queryParams.Encode()
   176  
   177  	return thisURI.String(), nil
   178  }
   179  
   180  func (a *TappxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   181  	if response.StatusCode == http.StatusNoContent {
   182  		return nil, nil
   183  	}
   184  
   185  	if response.StatusCode == http.StatusBadRequest {
   186  		return nil, []error{&errortypes.BadInput{
   187  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   188  		}}
   189  	}
   190  
   191  	if response.StatusCode != http.StatusOK {
   192  		return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}
   193  	}
   194  
   195  	var bidResp openrtb2.BidResponse
   196  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   197  		return nil, []error{err}
   198  	}
   199  
   200  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   201  
   202  	for _, sb := range bidResp.SeatBid {
   203  		for i := 0; i < len(sb.Bid); i++ {
   204  			bid := sb.Bid[i]
   205  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   206  				Bid:     &bid,
   207  				BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
   208  			})
   209  
   210  		}
   211  	}
   212  	return bidResponse, []error{}
   213  }
   214  
   215  func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
   216  	mediaType := openrtb_ext.BidTypeBanner
   217  	for _, imp := range imps {
   218  		if imp.ID == impId {
   219  			if imp.Video != nil {
   220  				mediaType = openrtb_ext.BidTypeVideo
   221  			}
   222  			return mediaType
   223  		}
   224  	}
   225  	return mediaType
   226  }