Initial Marktplaats SDK scaffold
Clone the twdehands SDK into mrktplaats and retarget naming and defaults for app.marktplaats.nl while keeping the same request/response bodies and endpoint behavior.
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
package mrktplaats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SearchService handles listing search and keyword suggestions.
|
||||
type SearchService struct {
|
||||
t *transport
|
||||
}
|
||||
|
||||
// DefaultSupportedTypes is the standard set of item types the client declares support for.
|
||||
var DefaultSupportedTypes = []string{
|
||||
"defaultListing",
|
||||
"icasTopBlockListing",
|
||||
"icasSiblingListing",
|
||||
"sellerHeaderInformation",
|
||||
"srpHeaderNativeAd",
|
||||
"srpListNativeAd",
|
||||
"displayBanner",
|
||||
"similarItems",
|
||||
"srpSaveSearchFooter",
|
||||
"lrpDisclaimer",
|
||||
"shippingPromotion",
|
||||
"dac7banner",
|
||||
"knnInfoBox",
|
||||
}
|
||||
|
||||
// SearchRequest defines the parameters for a listing search.
|
||||
type SearchRequest struct {
|
||||
CategoryID *int `json:"categoryId,omitempty"`
|
||||
Query string `json:"q"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
SortBy string `json:"sortBy"`
|
||||
Languages []string `json:"languages,omitempty"`
|
||||
Latitude float64 `json:"latitude,omitempty"`
|
||||
Longitude float64 `json:"longitude,omitempty"`
|
||||
AllowCorrection bool `json:"allowCorrection"`
|
||||
SearchOnTitleAndDescription bool `json:"searchOnTitleAndDescription"`
|
||||
ShowListings bool `json:"showListings"`
|
||||
ShowSimilarItems bool `json:"showSimilarItems"`
|
||||
SupportsReservedFlag bool `json:"supportsReservedFlag"`
|
||||
LrpDisclaimerDismissTime int `json:"lrpDisclaimerDismissTime"`
|
||||
AvailableTilesInLastRow int `json:"availableTilesInLastRow"`
|
||||
SellerID string `json:"sellerId,omitempty"`
|
||||
AttributesById []AttributeFilter `json:"attributesById,omitempty"`
|
||||
}
|
||||
|
||||
// AttributeFilter filters search results by a specific attribute key-value pair.
|
||||
type AttributeFilter struct {
|
||||
AttributeKeyID string `json:"attributeKeyId"`
|
||||
AttributeValueID string `json:"attributeValueId"`
|
||||
}
|
||||
|
||||
type searchRequestEnvelope struct {
|
||||
SearchRequest SearchRequest `json:"searchRequest"`
|
||||
SupportedTypes []string `json:"supportedTypes"`
|
||||
}
|
||||
|
||||
// SearchResponse contains the search results.
|
||||
type SearchResponse struct {
|
||||
Items []SearchItem `json:"items"`
|
||||
SearchHistograms *SearchHistograms `json:"searchHistograms,omitempty"`
|
||||
CorrelationID string `json:"correlationId"`
|
||||
HasErrors bool `json:"hasErrors"`
|
||||
CategoryID int `json:"categoryId"`
|
||||
BigViewAllowed bool `json:"bigViewAllowed"`
|
||||
}
|
||||
|
||||
// SearchHistograms contains facet/filter information from a search.
|
||||
type SearchHistograms struct {
|
||||
NumFound int `json:"numFound"`
|
||||
Attributes json.RawMessage `json:"attributes,omitempty"`
|
||||
Categories json.RawMessage `json:"categories,omitempty"`
|
||||
SortableFields json.RawMessage `json:"sortableFields,omitempty"`
|
||||
}
|
||||
|
||||
// SearchItem is a single item in search results. The ItemType field
|
||||
// determines which field is populated.
|
||||
type SearchItem struct {
|
||||
ItemType string `json:"itemType"`
|
||||
DefaultListing *DefaultListing `json:"defaultListing,omitempty"`
|
||||
IcasTopBlockListing *DefaultListing `json:"icasTopBlockListing,omitempty"`
|
||||
IcasSiblingListing *DefaultListing `json:"icasSiblingListing,omitempty"`
|
||||
}
|
||||
|
||||
// Listing returns the first non-nil listing from the item, regardless of type.
|
||||
// Returns nil for non-listing item types (banners, disclaimers, etc.).
|
||||
func (i *SearchItem) Listing() *DefaultListing {
|
||||
switch {
|
||||
case i.DefaultListing != nil:
|
||||
return i.DefaultListing
|
||||
case i.IcasTopBlockListing != nil:
|
||||
return i.IcasTopBlockListing
|
||||
case i.IcasSiblingListing != nil:
|
||||
return i.IcasSiblingListing
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultListing represents a standard listing in search results.
|
||||
type DefaultListing struct {
|
||||
AdCore AdCore `json:"adCore"`
|
||||
DistanceInMeter int `json:"distanceInMeter,omitempty"`
|
||||
LocationDescription string `json:"locationDescription,omitempty"`
|
||||
SellerInformation SellerInformation `json:"sellerInformation"`
|
||||
URL string `json:"url,omitempty"`
|
||||
ExtraImages json.RawMessage `json:"extraImages,omitempty"`
|
||||
Thumbnails json.RawMessage `json:"thumbnails,omitempty"`
|
||||
TotalPictures int `json:"totalPictures,omitempty"`
|
||||
AdType string `json:"adType,omitempty"`
|
||||
SortDateTime string `json:"sortDateTime,omitempty"`
|
||||
Flags json.RawMessage `json:"flags,omitempty"`
|
||||
SavedAdForUser bool `json:"savedAdForUser,omitempty"`
|
||||
Shippable bool `json:"shippable,omitempty"`
|
||||
Reserved bool `json:"reserved,omitempty"`
|
||||
PageLocation string `json:"pageLocation,omitempty"`
|
||||
}
|
||||
|
||||
// KeywordSuggestionsResponse is the response from keyword autocomplete.
|
||||
type KeywordSuggestionsResponse struct {
|
||||
Keyword string `json:"keyword"`
|
||||
Category int `json:"category"`
|
||||
Suggestions []string `json:"suggestions"`
|
||||
}
|
||||
|
||||
// KeywordSuggestions returns autocomplete suggestions for a search prefix.
|
||||
func (s *SearchService) KeywordSuggestions(ctx context.Context, prefix string, category int) (*KeywordSuggestionsResponse, error) {
|
||||
params := url.Values{
|
||||
"prefix": {prefix},
|
||||
"category": {fmt.Sprintf("%d", category)},
|
||||
}
|
||||
var resp KeywordSuggestionsResponse
|
||||
if err := s.t.get(ctx, "/app/lrp/v1/keyword-suggestions", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Fetch performs a listing search with the given request.
|
||||
// If supportedTypes is nil, DefaultSupportedTypes is used.
|
||||
func (s *SearchService) Fetch(ctx context.Context, req *SearchRequest, supportedTypes []string) (*SearchResponse, error) {
|
||||
if supportedTypes == nil {
|
||||
supportedTypes = DefaultSupportedTypes
|
||||
}
|
||||
envelope := searchRequestEnvelope{
|
||||
SearchRequest: *req,
|
||||
SupportedTypes: supportedTypes,
|
||||
}
|
||||
|
||||
var resp SearchResponse
|
||||
if err := s.t.postJSON(ctx, "/app/lrp/v1/items/fetch", nil, nil, envelope, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// DisplayTargetingRequest defines parameters for search ad targeting.
|
||||
// Positions is a slice of position strings that will be serialized as a
|
||||
// comma-separated string as required by the API.
|
||||
// Postcode and SellerID are sent as JSON null when empty.
|
||||
type DisplayTargetingRequest struct {
|
||||
Attr json.RawMessage
|
||||
Positions []string
|
||||
Query string
|
||||
SearchCategoryID int
|
||||
Postcode string
|
||||
SellerID string
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the request in the format the marktplaats API expects:
|
||||
// positions as a comma-separated string, attr as {} when nil, and
|
||||
// postcode/sellerId as null when empty.
|
||||
func (r DisplayTargetingRequest) MarshalJSON() ([]byte, error) {
|
||||
attr := r.Attr
|
||||
if len(attr) == 0 {
|
||||
attr = json.RawMessage(`{}`)
|
||||
}
|
||||
type wire struct {
|
||||
Attr json.RawMessage `json:"attr"`
|
||||
Positions string `json:"positions"`
|
||||
Query string `json:"q"`
|
||||
SearchCategoryID int `json:"searchCategoryId"`
|
||||
Postcode *string `json:"postcode"`
|
||||
SellerID *string `json:"sellerId"`
|
||||
}
|
||||
w := wire{
|
||||
Attr: attr,
|
||||
Positions: strings.Join(r.Positions, ", "),
|
||||
Query: r.Query,
|
||||
SearchCategoryID: r.SearchCategoryID,
|
||||
}
|
||||
if r.Postcode != "" {
|
||||
w.Postcode = &r.Postcode
|
||||
}
|
||||
if r.SellerID != "" {
|
||||
w.SellerID = &r.SellerID
|
||||
}
|
||||
return json.Marshal(w)
|
||||
}
|
||||
|
||||
// DisplayTargetingResult is a single ad slot targeting result.
|
||||
type DisplayTargetingResult struct {
|
||||
Position string `json:"position"`
|
||||
AdUnitID string `json:"adUnitId"`
|
||||
AdditionalParameters map[string][]string `json:"additionalParameters"`
|
||||
}
|
||||
|
||||
// DisplayTargeting retrieves ad targeting configuration for search results.
|
||||
func (s *SearchService) DisplayTargeting(ctx context.Context, req *DisplayTargetingRequest) ([]DisplayTargetingResult, error) {
|
||||
var resp []DisplayTargetingResult
|
||||
if err := s.t.postJSON(ctx, "/app/lrp/v1/display-targeting", nil, nil, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user