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 }