Files
mrktplaats/syi.go
T
Joren 25410749db 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.
2026-04-15 23:45:49 +02:00

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
}