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.
210 lines
7.6 KiB
Go
210 lines
7.6 KiB
Go
package mrktplaats
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/url"
|
|
)
|
|
|
|
// SYIService handles creating listings (Sell Your Item).
|
|
type SYIService struct {
|
|
t *transport
|
|
}
|
|
|
|
// SYIAttribute describes a form field for creating a listing in a category.
|
|
type SYIAttribute struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Name string `json:"name"`
|
|
AttributeType string `json:"attributeType"`
|
|
Mandatory bool `json:"mandatory"`
|
|
SupportedValues []SYIAttributeValue `json:"supportedValues"`
|
|
}
|
|
|
|
// SYIAttributeValue is a selectable value for an SYI attribute.
|
|
type SYIAttributeValue struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// SYIFormResponse is returned when fetching the SYI form for a category.
|
|
type SYIFormResponse struct {
|
|
CategoryID int `json:"categoryId"`
|
|
SYIAttributes []SYIAttribute `json:"syiAttributes"`
|
|
}
|
|
|
|
// Form retrieves the listing creation form for a category.
|
|
func (s *SYIService) Form(ctx context.Context, categoryID int) (*SYIFormResponse, error) {
|
|
path := fmt.Sprintf("/app/syi/v7/%d", categoryID)
|
|
var resp SYIFormResponse
|
|
if err := s.t.get(ctx, path, nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// Translation holds locale-specific title and description.
|
|
type Translation struct {
|
|
Locale string `json:"locale"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
// ShippingConfig defines shipping carrier configuration.
|
|
type ShippingConfig struct {
|
|
Carriers []string `json:"carriers"`
|
|
DIYPriceInCent *int `json:"diyPriceInCent,omitempty"`
|
|
PackageID *string `json:"packageId,omitempty"`
|
|
}
|
|
|
|
// CreateAdRequest defines the payload for creating a new listing.
|
|
type CreateAdRequest struct {
|
|
CategoryID int `json:"categoryId"`
|
|
Translations []Translation `json:"translations"`
|
|
PriceInCents int `json:"priceInCents"`
|
|
PriceType string `json:"priceType"`
|
|
BiddingEnabled bool `json:"biddingEnabled"`
|
|
BuyItNowEnabled bool `json:"buyItNowEnabled"`
|
|
BuyerProtectionAllowed bool `json:"buyerProtectionAllowed"`
|
|
InitialMinimumBid int `json:"initialMinimumBid,omitempty"`
|
|
Attributes []any `json:"attributes"`
|
|
PictureIDs []string `json:"pictureIds"`
|
|
DeliveryMethod string `json:"deliveryMethod"`
|
|
ShippingConfig ShippingConfig `json:"shippingConfig"`
|
|
SellerName string `json:"sellerName"`
|
|
Postcode string `json:"postcode"`
|
|
AddToPaymentCart bool `json:"addToPaymentCart"`
|
|
SelectedBundle string `json:"selectedBundle"`
|
|
SYISessionID string `json:"syiSessionId"`
|
|
IntegratedShippingForTWHEnabled bool `json:"integratedShippingForTWHEnabled"`
|
|
FeatureSource string `json:"featureSource,omitempty"`
|
|
FeatureTypes []any `json:"featureTypes,omitempty"`
|
|
URL *string `json:"url"`
|
|
LicensePlate *string `json:"licensePlate"`
|
|
MicroTip *string `json:"microTip"`
|
|
OriginalAd *string `json:"originalAd"`
|
|
PhoneNumber *string `json:"phoneNumber"`
|
|
GenAIVersion *string `json:"genAiVersion"`
|
|
UseDynamicPricing bool `json:"useDynamicPricing"`
|
|
}
|
|
|
|
// CreateAdData holds the ad and feature data returned after creating a listing.
|
|
type CreateAdData struct {
|
|
AdCore json.RawMessage `json:"adCore"`
|
|
Features json.RawMessage `json:"features"`
|
|
}
|
|
|
|
// CreateAdResponse is the response from creating a listing.
|
|
type CreateAdResponse struct {
|
|
Ad CreateAdData `json:"ad"`
|
|
}
|
|
|
|
// URN parses and returns the URN of the newly created listing.
|
|
func (r *CreateAdResponse) URN() string {
|
|
var core struct {
|
|
URN string `json:"urn"`
|
|
}
|
|
if err := json.Unmarshal(r.Ad.AdCore, &core); err != nil {
|
|
return ""
|
|
}
|
|
return core.URN
|
|
}
|
|
|
|
// Create publishes a new listing.
|
|
func (s *SYIService) Create(ctx context.Context, req *CreateAdRequest) (*CreateAdResponse, error) {
|
|
params := url.Values{"syiType": {"shortFlow"}}
|
|
var resp CreateAdResponse
|
|
if err := s.t.postJSON(ctx, "/app/syi/v7/ads", params, nil, req, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// UploadImage uploads an image for use in a listing.
|
|
// Returns the picture ID to include in CreateAdRequest.PictureIDs.
|
|
// Large images are automatically compressed to stay within the API size limit.
|
|
func (s *SYIService) UploadImage(ctx context.Context, img io.Reader, filename string) (string, error) {
|
|
data, err := autoCompressImage(img)
|
|
if err != nil {
|
|
return "", fmt.Errorf("mrktplaats: reading image: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
w := multipart.NewWriter(&buf)
|
|
part, err := w.CreateFormFile("image_file", filename)
|
|
if err != nil {
|
|
return "", fmt.Errorf("mrktplaats: creating form file: %w", err)
|
|
}
|
|
if _, err := io.Copy(part, bytes.NewReader(data)); err != nil {
|
|
return "", fmt.Errorf("mrktplaats: copying image: %w", err)
|
|
}
|
|
w.Close()
|
|
|
|
var resp struct {
|
|
PictureID string `json:"pictureId"`
|
|
}
|
|
if err := s.t.postMultipart(ctx, "/app/syi/v7/images/upload", nil, nil, w.FormDataContentType(), &buf, &resp); err != nil {
|
|
return "", err
|
|
}
|
|
return resp.PictureID, nil
|
|
}
|
|
|
|
// PriceSegment represents a price range suggestion.
|
|
type PriceSegment struct {
|
|
Title string `json:"title"`
|
|
MinPrice int `json:"minPrice"`
|
|
MaxPrice int `json:"maxPrice"`
|
|
TotalSimilarAdsCount int `json:"totalSimilarAdsCount"`
|
|
}
|
|
|
|
// PriceSuggestionResponse contains price range suggestions.
|
|
type PriceSuggestionResponse struct {
|
|
Segments []PriceSegment `json:"segments"`
|
|
}
|
|
|
|
// PriceSuggestion returns price range suggestions for a title in a category.
|
|
func (s *SYIService) PriceSuggestion(ctx context.Context, categoryID int, title string) (*PriceSuggestionResponse, error) {
|
|
params := url.Values{
|
|
"category_id": {fmt.Sprintf("%d", categoryID)},
|
|
"title": {title},
|
|
}
|
|
var resp PriceSuggestionResponse
|
|
if err := s.t.get(ctx, "/app/syi/v7/price-suggestion", params, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
// AttributeSuggestions returns suggested attribute values for a description.
|
|
func (s *SYIService) AttributeSuggestions(ctx context.Context, categoryID int, text string) (json.RawMessage, error) {
|
|
body := struct {
|
|
CategoryID int `json:"categoryId"`
|
|
Text string `json:"text"`
|
|
}{CategoryID: categoryID, Text: text}
|
|
var resp json.RawMessage
|
|
if err := s.t.postJSON(ctx, "/app/syi/v7/attribute-suggestions", nil, nil, body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// SuspiciousKeywordsWarnings checks a listing description for suspicious content.
|
|
func (s *SYIService) SuspiciousKeywordsWarnings(ctx context.Context, categoryID int, text string) (json.RawMessage, error) {
|
|
body := struct {
|
|
CategoryID int `json:"categoryId"`
|
|
Text string `json:"text"`
|
|
}{CategoryID: categoryID, Text: text}
|
|
var resp json.RawMessage
|
|
if err := s.t.postJSON(ctx, "/app/syi/v7/category/suspicious-keywords-warnings", nil, nil, body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|