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.
223 lines
8.2 KiB
Go
223 lines
8.2 KiB
Go
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
|
|
}
|