github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2"
    14  	"github.com/prebid/prebid-server/v2/adapters"
    15  	"github.com/prebid/prebid-server/v2/config"
    16  	"github.com/prebid/prebid-server/v2/errortypes"
    17  	"github.com/prebid/prebid-server/v2/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  			ImpIDs:  []string{request.Imp[index].ID},
    81  		}
    82  		bidRequestArray = append(bidRequestArray, bidRequest)
    83  	}
    84  
    85  	return bidRequestArray, errs
    86  }
    87  
    88  func (adg *AdgenerationAdapter) getRequestUri(request *openrtb2.BidRequest, index int) (string, error) {
    89  	imp := request.Imp[index]
    90  	adgExt, err := unmarshalExtImpAdgeneration(&imp)
    91  	if err != nil {
    92  		return "", &errortypes.BadInput{
    93  			Message: err.Error(),
    94  		}
    95  	}
    96  	uriObj, err := url.Parse(adg.endpoint)
    97  	if err != nil {
    98  		return "", &errortypes.BadInput{
    99  			Message: err.Error(),
   100  		}
   101  	}
   102  	v := adg.getRawQuery(adgExt.Id, request, &imp)
   103  	uriObj.RawQuery = v.Encode()
   104  	return uriObj.String(), err
   105  }
   106  
   107  func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb2.BidRequest, imp *openrtb2.Imp) *url.Values {
   108  	v := url.Values{}
   109  	v.Set("posall", "SSPLOC")
   110  	v.Set("id", id)
   111  	v.Set("hb", "true")
   112  	v.Set("t", "json3")
   113  	v.Set("currency", adg.getCurrency(request))
   114  	v.Set("sdkname", "prebidserver")
   115  	v.Set("adapterver", adg.version)
   116  	adSize := getSizes(imp)
   117  	if adSize != "" {
   118  		v.Set("sizes", adSize)
   119  	}
   120  	if request.Device != nil && request.Device.OS == "android" {
   121  		v.Set("sdktype", "1")
   122  	} else if request.Device != nil && request.Device.OS == "ios" {
   123  		v.Set("sdktype", "2")
   124  	} else {
   125  		v.Set("sdktype", "0")
   126  	}
   127  	if request.Site != nil && request.Site.Page != "" {
   128  		v.Set("tp", request.Site.Page)
   129  	}
   130  	if request.Source != nil && request.Source.TID != "" {
   131  		v.Set("transactionid", request.Source.TID)
   132  	}
   133  	if request.App != nil && request.App.Bundle != "" {
   134  		v.Set("appbundle", request.App.Bundle)
   135  	}
   136  	if request.App != nil && request.App.Name != "" {
   137  		v.Set("appname", request.App.Name)
   138  	}
   139  	if request.Device != nil && request.Device.OS == "ios" && request.Device.IFA != "" {
   140  		v.Set("idfa", request.Device.IFA)
   141  	}
   142  	if request.Device != nil && request.Device.OS == "android" && request.Device.IFA != "" {
   143  		v.Set("advertising_id", request.Device.IFA)
   144  	}
   145  
   146  	return &v
   147  }
   148  
   149  func unmarshalExtImpAdgeneration(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdgeneration, error) {
   150  	var bidderExt adapters.ExtImpBidder
   151  	var adgExt openrtb_ext.ExtImpAdgeneration
   152  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   153  		return nil, err
   154  	}
   155  	if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil {
   156  		return nil, err
   157  	}
   158  	if adgExt.Id == "" {
   159  		return nil, errors.New("No Location ID in ExtImpAdgeneration.")
   160  	}
   161  	return &adgExt, nil
   162  }
   163  
   164  func getSizes(imp *openrtb2.Imp) string {
   165  	if imp.Banner == nil || len(imp.Banner.Format) == 0 {
   166  		return ""
   167  	}
   168  	var sizeStr string
   169  	for _, v := range imp.Banner.Format {
   170  		sizeStr += strconv.FormatInt(v.W, 10) + "x" + strconv.FormatInt(v.H, 10) + ","
   171  	}
   172  	if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 {
   173  		sizeStr = sizeStr[:len(sizeStr)-1]
   174  	}
   175  	return sizeStr
   176  }
   177  
   178  func (adg *AdgenerationAdapter) getCurrency(request *openrtb2.BidRequest) string {
   179  	if len(request.Cur) <= 0 {
   180  		return adg.defaultCurrency
   181  	} else {
   182  		for _, c := range request.Cur {
   183  			if adg.defaultCurrency == c {
   184  				return c
   185  			}
   186  		}
   187  		return request.Cur[0]
   188  	}
   189  }
   190  
   191  func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   192  	if response.StatusCode == http.StatusNoContent {
   193  		return nil, nil
   194  	}
   195  	if response.StatusCode == http.StatusBadRequest {
   196  		return nil, []error{&errortypes.BadInput{
   197  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   198  		}}
   199  	}
   200  	if response.StatusCode != http.StatusOK {
   201  		return nil, []error{&errortypes.BadServerResponse{
   202  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   203  		}}
   204  	}
   205  	var bidResp adgServerResponse
   206  	err := json.Unmarshal(response.Body, &bidResp)
   207  	if err != nil {
   208  		return nil, []error{err}
   209  	}
   210  	if len(bidResp.Results) <= 0 {
   211  		return nil, nil
   212  	}
   213  
   214  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   215  	var impId string
   216  	var bitType openrtb_ext.BidType
   217  	var adm string
   218  	for _, v := range internalRequest.Imp {
   219  		adgExt, err := unmarshalExtImpAdgeneration(&v)
   220  		if err != nil {
   221  			return nil, []error{&errortypes.BadServerResponse{
   222  				Message: err.Error(),
   223  			},
   224  			}
   225  		}
   226  		if adgExt.Id == bidResp.Locationid {
   227  			impId = v.ID
   228  			bitType = openrtb_ext.BidTypeBanner
   229  			adm = createAd(&bidResp, impId)
   230  			bid := openrtb2.Bid{
   231  				ID:     bidResp.Locationid,
   232  				ImpID:  impId,
   233  				AdM:    adm,
   234  				Price:  bidResp.Cpm,
   235  				W:      int64(bidResp.W),
   236  				H:      int64(bidResp.H),
   237  				CrID:   bidResp.Creativeid,
   238  				DealID: bidResp.Dealid,
   239  			}
   240  
   241  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   242  				Bid:     &bid,
   243  				BidType: bitType,
   244  			})
   245  			bidResponse.Currency = adg.getCurrency(internalRequest)
   246  			return bidResponse, nil
   247  		}
   248  	}
   249  	return nil, nil
   250  }
   251  
   252  func createAd(body *adgServerResponse, impId string) string {
   253  	ad := body.Ad
   254  	if body.Vastxml != "" {
   255  		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>"
   256  	}
   257  	ad = appendChildToBody(ad, body.Beacon)
   258  	unwrappedAd := removeWrapper(ad)
   259  	if unwrappedAd != "" {
   260  		return unwrappedAd
   261  	}
   262  	return ad
   263  }
   264  
   265  func insertVASTMethod(bidId string, vastxml string) string {
   266  	rep := regexp.MustCompile(`/\r?\n/g`)
   267  	var replacedVastxml = rep.ReplaceAllString(vastxml, "")
   268  	return "<script type=\"text/javascript\"> (function(){ new APV.VideoAd({s:\"" + bidId + "\"}).load('" + replacedVastxml + "'); })(); </script>"
   269  }
   270  
   271  func appendChildToBody(ad string, data string) string {
   272  	rep := regexp.MustCompile(`<\/\s?body>`)
   273  	return rep.ReplaceAllString(ad, data+"</body>")
   274  }
   275  
   276  func removeWrapper(ad string) string {
   277  	bodyIndex := strings.Index(ad, "<body>")
   278  	lastBodyIndex := strings.LastIndex(ad, "</body>")
   279  	if bodyIndex == -1 || lastBodyIndex == -1 {
   280  		return ""
   281  	}
   282  
   283  	str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "<body>", "", 1), "</body>", "", 1))
   284  	return str
   285  }
   286  
   287  // Builder builds a new instance of the Adgeneration adapter for the given bidder with the given config.
   288  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   289  	bidder := &AdgenerationAdapter{
   290  		config.Endpoint,
   291  		"1.0.3",
   292  		"JPY",
   293  	}
   294  	return bidder, nil
   295  }