diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go new file mode 100644 index 00000000000..4d46ffb8f1e --- /dev/null +++ b/adapters/inmobi/inmobi.go @@ -0,0 +1,127 @@ +package inmobi + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type InMobiAdapter struct { + endPoint string +} + +func NewInMobiAdapter(endpoint string) *InMobiAdapter { + return &InMobiAdapter{ + endPoint: endpoint, + } +} + +func (a *InMobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impression in the request", + }} + } + + if err := preprocess(&request.Imp[0]); err != nil { + errs = append(errs, err) + return nil, errs + } + + reqJson, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endPoint, + Body: reqJson, + Headers: headers, + }}, errs +} + +func (a *InMobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected http status code: %d", response.StatusCode), + }} + } + + var serverBidResponse openrtb.BidResponse + if err := json.Unmarshal(response.Body, &serverBidResponse); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range serverBidResponse.SeatBid { + for i := range sb.Bid { + mediaType := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + + return bidResponse, nil +} + +func preprocess(imp *openrtb.Imp) error { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return &errortypes.BadInput{ + Message: err.Error(), + } + } + + var inMobiExt openrtb_ext.ExtImpInMobi + if err := json.Unmarshal(bidderExt.Bidder, &inMobiExt); err != nil { + return &errortypes.BadInput{Message: "bad InMobi bidder ext"} + } + + if len(inMobiExt.Plc) == 0 { + return &errortypes.BadInput{Message: "'plc' is a required attribute for InMobi's bidder ext"} + } + + if imp.Banner != nil { + banner := *imp.Banner + imp.Banner = &banner + if (banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0) && len(banner.Format) > 0 { + format := banner.Format[0] + banner.W = &format.W + banner.H = &format.H + } + } + + return nil +} + +func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + break + } + } + return mediaType +} diff --git a/adapters/inmobi/inmobi_test.go b/adapters/inmobi/inmobi_test.go new file mode 100644 index 00000000000..6aa58d97222 --- /dev/null +++ b/adapters/inmobi/inmobi_test.go @@ -0,0 +1,10 @@ +package inmobi + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "inmobitest", NewInMobiAdapter("https://api.w.inmobi.com/showad/openrtb/bidder/prebid")) +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..4345ef8ff66 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-banner.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-video.json new file mode 100644 index 00000000000..20b3c0cc810 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-video.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1598991608990" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1598991608990" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": " ", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": " ", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "video" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/params/race/banner.json b/adapters/inmobi/inmobitest/params/race/banner.json new file mode 100644 index 00000000000..7791393fc99 --- /dev/null +++ b/adapters/inmobi/inmobitest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "plc": "1596825400965" +} diff --git a/adapters/inmobi/inmobitest/params/race/video.json b/adapters/inmobi/inmobitest/params/race/video.json new file mode 100644 index 00000000000..74a44b6e6f9 --- /dev/null +++ b/adapters/inmobi/inmobitest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "plc": "1598991608990" +} diff --git a/adapters/inmobi/inmobitest/supplemental/204.json b/adapters/inmobi/inmobitest/supplemental/204.json new file mode 100644 index 00000000000..c811763678c --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/204.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }] +} diff --git a/adapters/inmobi/inmobitest/supplemental/400.json b/adapters/inmobi/inmobitest/supplemental/400.json new file mode 100644 index 00000000000..2df5c85aaca --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 400", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json new file mode 100644 index 00000000000..514f86817c9 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "device-ifa", + "ip": "1.1.1.1", + "ua": "device-ua" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "format": [{ + "w": 320, + "h": 50 + }] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "device-ifa", + "ip": "1.1.1.1", + "ua": "device-ua" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1596825400965" + } + }, + "banner": { + "format": [{ + "w": 320, + "h": 50 + }], + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json new file mode 100644 index 00000000000..957a2f6f952 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/ext-unmarshal-err.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + { + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "plc": true + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "bad InMobi bidder ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json new file mode 100644 index 00000000000..52697cf90c6 --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/missing-plc-error.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + { + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "a": 1 + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "'plc' is a required attribute for InMobi's bidder ext", + "comparison": "literal" + } + ] +} diff --git a/adapters/inmobi/inmobitest/supplemental/no-imp-error.json b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json new file mode 100644 index 00000000000..6c6c363425a --- /dev/null +++ b/adapters/inmobi/inmobitest/supplemental/no-imp-error.json @@ -0,0 +1,13 @@ +{ + "mockBidRequest": { + "id": "req-id", + "imp": [ + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the request", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 5731b65d567..53daf117fdf 100755 --- a/config/config.go +++ b/config/config.go @@ -926,6 +926,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") + v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 5bb788b63b9..d428168921a 100755 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -45,6 +45,7 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/kubient" @@ -135,6 +136,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderGrid: grid.NewGridBidder(cfg.Adapters[string(openrtb_ext.BidderGrid)].Endpoint), openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), + openrtb_ext.BidderInMobi: inmobi.NewInMobiAdapter(cfg.Adapters[string(openrtb_ext.BidderInMobi)].Endpoint), openrtb_ext.BidderKidoz: kidoz.NewKidozBidder(cfg.Adapters[string(openrtb_ext.BidderKidoz)].Endpoint), openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderLockerDome: lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 221f97c9697..dcfd663ebc7 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -64,6 +64,7 @@ const ( BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" BidderImprovedigital BidderName = "improvedigital" + BidderInMobi BidderName = "inmobi" BidderIx BidderName = "ix" BidderKidoz BidderName = "kidoz" BidderKubient BidderName = "kubient" @@ -149,6 +150,7 @@ var BidderMap = map[string]BidderName{ "grid": BidderGrid, "gumgum": BidderGumGum, "improvedigital": BidderImprovedigital, + "inmobi": BidderInMobi, "ix": BidderIx, "kidoz": BidderKidoz, "kubient": BidderKubient, diff --git a/openrtb_ext/imp_inmobi.go b/openrtb_ext/imp_inmobi.go new file mode 100644 index 00000000000..d74e3cac8b0 --- /dev/null +++ b/openrtb_ext/imp_inmobi.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpInMobi struct { + Plc string `json:"plc"` +} diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml new file mode 100644 index 00000000000..3f8cdd8cb91 --- /dev/null +++ b/static/bidder-info/inmobi.yaml @@ -0,0 +1,8 @@ +maintainer: + email: "prebid-support@inmobi.com" + +capabilities: + app: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/inmobi.json b/static/bidder-params/inmobi.json new file mode 100644 index 00000000000..631b3137b72 --- /dev/null +++ b/static/bidder-params/inmobi.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "InMobi Adapter Params", + "description": "A schema which validates params accepted by the InMobi adapter", + "type": "object", + "properties": { + "plc": { + "type": ["string"], + "description": "An ID corresponding to the placement selling the impression" + } + }, + "required": ["plc"] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 2cf0b2513c5..bd250489fdd 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -89,6 +89,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdhese: true, openrtb_ext.BidderAdoppler: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderInMobi: true, openrtb_ext.BidderKidoz: true, openrtb_ext.BidderKubient: true, openrtb_ext.BidderMobileFuse: true,