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 }