github.com/prebid/prebid-server@v0.275.0/adapters/telaria/telaria.go (about) 1 package telaria 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 9 "github.com/prebid/openrtb/v19/openrtb2" 10 "github.com/prebid/prebid-server/adapters" 11 "github.com/prebid/prebid-server/config" 12 "github.com/prebid/prebid-server/errortypes" 13 "github.com/prebid/prebid-server/openrtb_ext" 14 ) 15 16 const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" 17 18 type TelariaAdapter struct { 19 URI string 20 } 21 22 // This will be part of imp[i].ext when this adapter calls out the Telaria Ad Server 23 type ImpressionExtOut struct { 24 OriginalTagID string `json:"originalTagid"` 25 OriginalPublisherID string `json:"originalPublisherid"` 26 } 27 28 type telariaBidExt struct { 29 Extra json.RawMessage `json:"extra,omitempty"` 30 } 31 32 // Endpoint for Telaria Ad server 33 func (a *TelariaAdapter) FetchEndpoint() string { 34 return a.URI 35 } 36 37 // Checker method to ensure len(request.Imp) > 0 38 func (a *TelariaAdapter) CheckHasImps(request *openrtb2.BidRequest) error { 39 if len(request.Imp) == 0 { 40 err := &errortypes.BadInput{ 41 Message: "Telaria: Missing Imp Object", 42 } 43 return err 44 } 45 return nil 46 } 47 48 // Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist 49 func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb2.BidRequest) error { 50 hasVideoObject := false 51 52 for _, imp := range request.Imp { 53 if imp.Banner != nil { 54 return &errortypes.BadInput{ 55 Message: "Telaria: Banner not supported", 56 } 57 } 58 59 hasVideoObject = hasVideoObject || imp.Video != nil 60 } 61 62 if !hasVideoObject { 63 return &errortypes.BadInput{ 64 Message: "Telaria: Only Supports Video", 65 } 66 } 67 68 return nil 69 } 70 71 // Fetches the populated header object 72 func GetHeaders(request *openrtb2.BidRequest) *http.Header { 73 headers := http.Header{} 74 headers.Add("Content-Type", "application/json;charset=utf-8") 75 headers.Add("Accept", "application/json") 76 headers.Add("X-Openrtb-Version", "2.5") 77 78 if request.Device != nil { 79 if len(request.Device.UA) > 0 { 80 headers.Add("User-Agent", request.Device.UA) 81 } 82 83 if len(request.Device.IP) > 0 { 84 headers.Add("X-Forwarded-For", request.Device.IP) 85 } 86 87 if len(request.Device.Language) > 0 { 88 headers.Add("Accept-Language", request.Device.Language) 89 } 90 91 if request.Device.DNT != nil { 92 headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT))) 93 } 94 } 95 96 return &headers 97 } 98 99 // Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format 100 func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTelaria, error) { 101 var bidderExt adapters.ExtImpBidder 102 err := json.Unmarshal(imp.Ext, &bidderExt) 103 104 if err != nil { 105 err = &errortypes.BadInput{ 106 Message: "Telaria: ext.bidder not provided", 107 } 108 109 return nil, err 110 } 111 112 var telariaExt openrtb_ext.ExtImpTelaria 113 err = json.Unmarshal(bidderExt.Bidder, &telariaExt) 114 115 if err != nil { 116 return nil, err 117 } 118 119 if telariaExt.SeatCode == "" { 120 return nil, &errortypes.BadInput{Message: "Telaria: Seat Code required"} 121 } 122 123 return &telariaExt, nil 124 } 125 126 // Method to fetch the original publisher ID. Note that this method must be called 127 // before we replace publisher.ID with seatCode 128 func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb2.BidRequest) string { 129 130 if request.Site != nil && request.Site.Publisher != nil { 131 return request.Site.Publisher.ID 132 } else if request.App != nil && request.App.Publisher != nil { 133 return request.App.Publisher.ID 134 } 135 136 return "" 137 } 138 139 // Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID 140 func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb2.Publisher) *openrtb2.Publisher { 141 var pub = &openrtb2.Publisher{ID: seatCode} 142 143 if publisher != nil { 144 pub.Domain = publisher.Domain 145 pub.Name = publisher.Name 146 pub.Cat = publisher.Cat 147 pub.Ext = publisher.Ext 148 } 149 150 return pub 151 } 152 153 // This method changes <site/app>.publisher.id to the seatCode 154 func (a *TelariaAdapter) PopulatePublisherId(request *openrtb2.BidRequest, seatCode string) (*openrtb2.Site, *openrtb2.App) { 155 if request.Site != nil { 156 siteCopy := *request.Site 157 siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher) 158 return &siteCopy, nil 159 } else if request.App != nil { 160 appCopy := *request.App 161 appCopy.Publisher = a.MakePublisherObject(seatCode, request.App.Publisher) 162 return nil, &appCopy 163 } 164 return nil, nil 165 } 166 167 func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 168 169 // make a copy of the incoming request 170 request := *requestIn 171 172 // ensure that the request has Impressions 173 if noImps := a.CheckHasImps(&request); noImps != nil { 174 return nil, []error{noImps} 175 } 176 177 // ensure that the request has a Video object 178 if noVideoObjectError := a.CheckHasVideoObject(&request); noVideoObjectError != nil { 179 return nil, []error{noVideoObjectError} 180 } 181 182 var seatCode string 183 originalPublisherID := a.FetchOriginalPublisherID(&request) 184 185 var telariaImpExt *openrtb_ext.ExtImpTelaria 186 var err error 187 188 var imp = request.Imp[0] 189 // fetch adCode & seatCode from imp[i].ext 190 telariaImpExt, err = a.FetchTelariaExtImpParams(&imp) 191 if err != nil { 192 return nil, []error{err} 193 } 194 195 seatCode = telariaImpExt.SeatCode 196 197 // move the original tagId and the original publisher.id into the imp[i].ext object 198 imp.Ext, err = json.Marshal(&ImpressionExtOut{imp.TagID, originalPublisherID}) 199 if err != nil { 200 return nil, []error{err} 201 } 202 203 // Swap the tagID with adCode 204 imp.TagID = telariaImpExt.AdCode 205 206 // Add the Extra from Imp to the top level Ext 207 if telariaImpExt != nil && telariaImpExt.Extra != nil { 208 request.Ext, err = json.Marshal(&telariaBidExt{Extra: telariaImpExt.Extra}) 209 if err != nil { 210 return nil, []error{err} 211 } 212 } 213 request.Imp = []openrtb2.Imp{imp} 214 215 // Add seatCode to <Site/App>.Publisher.ID 216 siteObject, appObject := a.PopulatePublisherId(&request, seatCode) 217 218 request.Site = siteObject 219 request.App = appObject 220 221 reqJSON, err := json.Marshal(request) 222 if err != nil { 223 return nil, []error{err} 224 } 225 226 return []*adapters.RequestData{{ 227 Method: "POST", 228 Uri: a.FetchEndpoint(), 229 Body: reqJSON, 230 Headers: *GetHeaders(&request), 231 }}, nil 232 } 233 234 func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error { 235 if response.StatusCode == http.StatusNoContent { 236 return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"} 237 } 238 239 if response.StatusCode == http.StatusBadRequest { 240 return &errortypes.BadInput{ 241 Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode), 242 } 243 } 244 245 if response.StatusCode == http.StatusServiceUnavailable { 246 return &errortypes.BadInput{ 247 Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), 248 } 249 } 250 251 if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { 252 return &errortypes.BadInput{ 253 Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), 254 } 255 } 256 257 return nil 258 } 259 260 func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 261 262 httpStatusError := a.CheckResponseStatusCodes(response) 263 if httpStatusError != nil { 264 return nil, []error{httpStatusError} 265 } 266 267 responseBody := response.Body 268 269 var bidResp openrtb2.BidResponse 270 if err := json.Unmarshal(responseBody, &bidResp); err != nil { 271 return nil, []error{&errortypes.BadServerResponse{ 272 Message: "Telaria: Bad Server Response", 273 }} 274 } 275 276 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 277 sb := bidResp.SeatBid[0] 278 279 for i := range sb.Bid { 280 bid := sb.Bid[i] 281 if i >= len(internalRequest.Imp) { 282 break 283 } 284 bid.ImpID = internalRequest.Imp[i].ID 285 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 286 Bid: &bid, 287 BidType: openrtb_ext.BidTypeVideo, 288 }) 289 } 290 return bidResponse, nil 291 } 292 293 // Builder builds a new instance of the Telaria adapter for the given bidder with the given config. 294 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 295 endpoint := config.Endpoint 296 if endpoint == "" { 297 endpoint = Endpoint // Hardcoded default 298 } 299 300 bidder := &TelariaAdapter{ 301 URI: endpoint, 302 } 303 return bidder, nil 304 }