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

     1  package adgeneration
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    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/openrtb_ext"
    18  )
    19  
    20  type AdgenerationAdapter struct {
    21  	endpoint        string
    22  	version         string
    23  	defaultCurrency string
    24  }
    25  
    26  // Server Responses
    27  type adgServerResponse struct {
    28  	Locationid string        `json:"locationid"`
    29  	Dealid     string        `json:"dealid"`
    30  	Ad         string        `json:"ad"`
    31  	Beacon     string        `json:"beacon"`
    32  	Beaconurl  string        `json:"beaconurl"`
    33  	Cpm        float64       `jsons:"cpm"`
    34  	Creativeid string        `json:"creativeid"`
    35  	H          uint64        `json:"h"`
    36  	W          uint64        `json:"w"`
    37  	Ttl        uint64        `json:"ttl"`
    38  	Vastxml    string        `json:"vastxml,omitempty"`
    39  	LandingUrl string        `json:"landing_url"`
    40  	Scheduleid string        `json:"scheduleid"`
    41  	Results    []interface{} `json:"results"`
    42  }
    43  
    44  func (adg *AdgenerationAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    45  	numRequests := len(request.Imp)
    46  	var errs []error
    47  
    48  	if numRequests == 0 {
    49  		errs = append(errs, &errortypes.BadInput{
    50  			Message: "No impression in the bid request",
    51  		})
    52  		return nil, errs
    53  	}
    54  
    55  	headers := http.Header{}
    56  	headers.Add("Content-Type", "application/json;charset=utf-8")
    57  	headers.Add("Accept", "application/json")
    58  	if request.Device != nil {
    59  		if len(request.Device.UA) > 0 {
    60  			headers.Add("User-Agent", request.Device.UA)
    61  		}
    62  		if len(request.Device.IP) > 0 {
    63  			headers.Add("X-Forwarded-For", request.Device.IP)
    64  		}
    65  	}
    66  
    67  	bidRequestArray := make([]*adapters.RequestData, 0, numRequests)
    68  
    69  	for index := 0; index < numRequests; index++ {
    70  		bidRequestUri, err := adg.getRequestUri(request, index)
    71  		if err != nil {
    72  			errs = append(errs, err)
    73  			return nil, errs
    74  		}
    75  		bidRequest := &adapters.RequestData{
    76  			Method:  "GET",
    77  			Uri:     bidRequestUri,
    78  			Body:    nil,
    79  			Headers: headers,
    80  		}
    81  		bidRequestArray = append(bidRequestArray, bidRequest)
    82  	}
    83  
    84  	return bidRequestArray, errs
    85  }
    86  
    87  func (adg *AdgenerationAdapter) getRequestUri(request *openrtb2.BidRequest, index int) (string, error) {
    88  	imp := request.Imp[index]
    89  	adgExt, err := unmarshalExtImpAdgeneration(&imp)
    90  	if err != nil {
    91  		return "", &errortypes.BadInput{
    92  			Message: err.Error(),
    93  		}
    94  	}
    95  	uriObj, err := url.Parse(adg.endpoint)
    96  	if err != nil {
    97  		return "", &errortypes.BadInput{
    98  			Message: err.Error(),
    99  		}
   100  	}
   101  	v := adg.getRawQuery(adgExt.Id, request, &imp)
   102  	uriObj.RawQuery = v.Encode()
   103  	return uriObj.String(), err
   104  }
   105  
   106  func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb2.BidRequest, imp *openrtb2.Imp) *url.Values {
   107  	v := url.Values{}
   108  	v.Set("posall", "SSPLOC")
   109  	v.Set("id", id)
   110  	v.Set("hb", "true")
   111  	v.Set("t", "json3")
   112  	v.Set("currency", adg.getCurrency(request))
   113  	v.Set("sdkname", "prebidserver")
   114  	v.Set("adapterver", adg.version)
   115  	adSize := getSizes(imp)
   116  	if adSize != "" {
   117  		v.Set("sizes", adSize)
   118  	}
   119  	if request.Device != nil && request.Device.OS == "android" {
   120  		v.Set("sdktype", "1")
   121  	} else if request.Device != nil && request.Device.OS == "ios" {
   122  		v.Set("sdktype", "2")
   123  	} else {
   124  		v.Set("sdktype", "0")
   125  	}
   126  	if request.Site != nil && request.Site.Page != "" {
   127  		v.Set("tp", request.Site.Page)
   128  	}
   129  	if request.Source != nil && request.Source.TID != "" {
   130  		v.Set("transactionid", request.Source.TID)
   131  	}
   132  	if request.App != nil && request.App.Bundle != "" {
   133  		v.Set("appbundle", request.App.Bundle)
   134  	}
   135  	if request.App != nil && request.App.Name != "" {
   136  		v.Set("appname", request.App.Name)
   137  	}
   138  	if request.Device != nil && request.Device.OS == "ios" && request.Device.IFA != "" {
   139  		v.Set("idfa", request.Device.IFA)
   140  	}
   141  	if request.Device != nil && request.Device.OS == "android" && request.Device.IFA != "" {
   142  		v.Set("advertising_id", request.Device.IFA)
   143  	}
   144  
   145  	return &v
   146  }
   147  
   148  func unmarshalExtImpAdgeneration(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdgeneration, error) {
   149  	var bidderExt adapters.ExtImpBidder
   150  	var adgExt openrtb_ext.ExtImpAdgeneration
   151  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   152  		return nil, err
   153  	}
   154  	if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil {
   155  		return nil, err
   156  	}
   157  	if adgExt.Id == "" {
   158  		return nil, errors.New("No Location ID in ExtImpAdgeneration.")
   159  	}
   160  	return &adgExt, nil
   161  }
   162  
   163  func getSizes(imp *openrtb2.Imp) string {
   164  	if imp.Banner == nil || len(imp.Banner.Format) == 0 {
   165  		return ""
   166  	}
   167  	var sizeStr string
   168  	for _, v := range imp.Banner.Format {
   169  		sizeStr += strconv.FormatInt(v.W, 10) + "x" + strconv.FormatInt(v.H, 10) + ","
   170  	}
   171  	if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 {
   172  		sizeStr = sizeStr[:len(sizeStr)-1]
   173  	}
   174  	return sizeStr
   175  }
   176  
   177  func (adg *AdgenerationAdapter) getCurrency(request *openrtb2.BidRequest) string {
   178  	if len(request.Cur) <= 0 {
   179  		return adg.defaultCurrency
   180  	} else {
   181  		for _, c := range request.Cur {
   182  			if adg.defaultCurrency == c {
   183  				return c
   184  			}
   185  		}
   186  		return request.Cur[0]
   187  	}
   188  }
   189  
   190  func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   191  	if response.StatusCode == http.StatusNoContent {
   192  		return nil, nil
   193  	}
   194  	if response.StatusCode == http.StatusBadRequest {
   195  		return nil, []error{&errortypes.BadInput{
   196  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   197  		}}
   198  	}
   199  	if response.StatusCode != http.StatusOK {
   200  		return nil, []error{&errortypes.BadServerResponse{
   201  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   202  		}}
   203  	}
   204  	var bidResp adgServerResponse
   205  	err := json.Unmarshal(response.Body, &bidResp)
   206  	if err != nil {
   207  		return nil, []error{err}
   208  	}
   209  	if len(bidResp.Results) <= 0 {
   210  		return nil, nil
   211  	}
   212  
   213  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   214  	var impId string
   215  	var bitType openrtb_ext.BidType
   216  	var adm string
   217  	for _, v := range internalRequest.Imp {
   218  		adgExt, err := unmarshalExtImpAdgeneration(&v)
   219  		if err != nil {
   220  			return nil, []error{&errortypes.BadServerResponse{
   221  				Message: err.Error(),
   222  			},
   223  			}
   224  		}
   225  		if adgExt.Id == bidResp.Locationid {
   226  			impId = v.ID
   227  			bitType = openrtb_ext.BidTypeBanner
   228  			adm = createAd(&bidResp, impId)
   229  			bid := openrtb2.Bid{
   230  				ID:     bidResp.Locationid,
   231  				ImpID:  impId,
   232  				AdM:    adm,
   233  				Price:  bidResp.Cpm,
   234  				W:      int64(bidResp.W),
   235  				H:      int64(bidResp.H),
   236  				CrID:   bidResp.Creativeid,
   237  				DealID: bidResp.Dealid,
   238  			}
   239  
   240  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   241  				Bid:     &bid,
   242  				BidType: bitType,
   243  			})
   244  			bidResponse.Currency = adg.getCurrency(internalRequest)
   245  			return bidResponse, nil
   246  		}
   247  	}
   248  	return nil, nil
   249  }
   250  
   251  func createAd(body *adgServerResponse, impId string) string {
   252  	ad := body.Ad
   253  	if body.Vastxml != "" {
   254  		ad = "<body><div id=\"apvad-" + impId + "\"></div><script type=\"text/javascript\" id=\"apv\" src=\"https://cdn.apvdr.com/js/VideoAd.min.js\"></script>" + insertVASTMethod(impId, body.Vastxml) + "</body>"
   255  	}
   256  	ad = appendChildToBody(ad, body.Beacon)
   257  	unwrappedAd := removeWrapper(ad)
   258  	if unwrappedAd != "" {
   259  		return unwrappedAd
   260  	}
   261  	return ad
   262  }
   263  
   264  func insertVASTMethod(bidId string, vastxml string) string {
   265  	rep := regexp.MustCompile(`/\r?\n/g`)
   266  	var replacedVastxml = rep.ReplaceAllString(vastxml, "")
   267  	return "<script type=\"text/javascript\"> (function(){ new APV.VideoAd({s:\"" + bidId + "\"}).load('" + replacedVastxml + "'); })(); </script>"
   268  }
   269  
   270  func appendChildToBody(ad string, data string) string {
   271  	rep := regexp.MustCompile(`<\/\s?body>`)
   272  	return rep.ReplaceAllString(ad, data+"</body>")
   273  }
   274  
   275  func removeWrapper(ad string) string {
   276  	bodyIndex := strings.Index(ad, "<body>")
   277  	lastBodyIndex := strings.LastIndex(ad, "</body>")
   278  	if bodyIndex == -1 || lastBodyIndex == -1 {
   279  		return ""
   280  	}
   281  
   282  	str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "<body>", "", 1), "</body>", "", 1))
   283  	return str
   284  }
   285  
   286  // Builder builds a new instance of the Adgeneration adapter for the given bidder with the given config.
   287  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   288  	bidder := &AdgenerationAdapter{
   289  		config.Endpoint,
   290  		"1.0.3",
   291  		"JPY",
   292  	}
   293  	return bidder, nil
   294  }