github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2" 10 "github.com/prebid/prebid-server/v2/adapters" 11 "github.com/prebid/prebid-server/v2/config" 12 "github.com/prebid/prebid-server/v2/errortypes" 13 "github.com/prebid/prebid-server/v2/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 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 232 }}, nil 233 } 234 235 func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error { 236 if response.StatusCode == http.StatusNoContent { 237 return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"} 238 } 239 240 if response.StatusCode == http.StatusBadRequest { 241 return &errortypes.BadInput{ 242 Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode), 243 } 244 } 245 246 if response.StatusCode == http.StatusServiceUnavailable { 247 return &errortypes.BadInput{ 248 Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), 249 } 250 } 251 252 if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { 253 return &errortypes.BadInput{ 254 Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), 255 } 256 } 257 258 return nil 259 } 260 261 func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 262 263 httpStatusError := a.CheckResponseStatusCodes(response) 264 if httpStatusError != nil { 265 return nil, []error{httpStatusError} 266 } 267 268 responseBody := response.Body 269 270 var bidResp openrtb2.BidResponse 271 if err := json.Unmarshal(responseBody, &bidResp); err != nil { 272 return nil, []error{&errortypes.BadServerResponse{ 273 Message: "Telaria: Bad Server Response", 274 }} 275 } 276 277 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 278 sb := bidResp.SeatBid[0] 279 280 for i := range sb.Bid { 281 bid := sb.Bid[i] 282 if i >= len(internalRequest.Imp) { 283 break 284 } 285 bid.ImpID = internalRequest.Imp[i].ID 286 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 287 Bid: &bid, 288 BidType: openrtb_ext.BidTypeVideo, 289 }) 290 } 291 return bidResponse, nil 292 } 293 294 // Builder builds a new instance of the Telaria adapter for the given bidder with the given config. 295 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 296 endpoint := config.Endpoint 297 if endpoint == "" { 298 endpoint = Endpoint // Hardcoded default 299 } 300 301 bidder := &TelariaAdapter{ 302 URI: endpoint, 303 } 304 return bidder, nil 305 }