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:
+219
@@ -0,0 +1,219 @@
|
||||
package mrktplaats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MessagingService handles conversations and messages.
|
||||
type MessagingService struct {
|
||||
t *transport
|
||||
}
|
||||
|
||||
// Conversation represents a messaging thread.
|
||||
type Conversation struct {
|
||||
ID string `json:"id"`
|
||||
ItemID string `json:"itemId"`
|
||||
SellerID int `json:"sellerId"`
|
||||
OtherParticipant Participant `json:"otherParticipant"`
|
||||
UnreadMessagesCount int `json:"unreadMessagesCount"`
|
||||
Title string `json:"title"`
|
||||
ImageURL string `json:"imageUrl"`
|
||||
Embedded conversationEmbed `json:"_embedded"`
|
||||
}
|
||||
|
||||
// LatestMessage returns the latest message in the conversation.
|
||||
func (c *Conversation) LatestMessage() *Message {
|
||||
return c.Embedded.LatestMessage
|
||||
}
|
||||
|
||||
type conversationEmbed struct {
|
||||
LatestMessage *Message `json:"mc:latest-message"`
|
||||
}
|
||||
|
||||
// Participant is the other user in a conversation.
|
||||
type Participant struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsReviewable bool `json:"isReviewable"`
|
||||
}
|
||||
|
||||
// Message represents a single message in a conversation.
|
||||
type Message struct {
|
||||
ID string `json:"id"`
|
||||
SenderID int `json:"senderId"`
|
||||
Text string `json:"text"`
|
||||
ReceivedDate time.Time `json:"receivedDate"`
|
||||
IsRead bool `json:"isRead"`
|
||||
MessageType string `json:"messageType"`
|
||||
IsMarkedAsFraud bool `json:"isMarkedAsFraud"`
|
||||
Actions []any `json:"actions"`
|
||||
}
|
||||
|
||||
// ConversationsResponse is the response from listing conversations.
|
||||
type ConversationsResponse struct {
|
||||
ConversationsWithUnreadMessagesCount int `json:"conversationsWithUnreadMessagesCount"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
UnreadMessagesCount int `json:"unreadMessagesCount"`
|
||||
Conversations []Conversation
|
||||
}
|
||||
|
||||
// UnmarshalJSON handles the HAL _embedded format for conversations.
|
||||
func (r *ConversationsResponse) UnmarshalJSON(data []byte) error {
|
||||
type plain struct {
|
||||
ConversationsWithUnreadMessagesCount int `json:"conversationsWithUnreadMessagesCount"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
UnreadMessagesCount int `json:"unreadMessagesCount"`
|
||||
Embedded struct {
|
||||
Conversations []Conversation `json:"mc:conversations"`
|
||||
} `json:"_embedded"`
|
||||
}
|
||||
var p plain
|
||||
if err := json.Unmarshal(data, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
r.ConversationsWithUnreadMessagesCount = p.ConversationsWithUnreadMessagesCount
|
||||
r.Limit = p.Limit
|
||||
r.Offset = p.Offset
|
||||
r.TotalCount = p.TotalCount
|
||||
r.UnreadMessagesCount = p.UnreadMessagesCount
|
||||
r.Conversations = p.Embedded.Conversations
|
||||
return nil
|
||||
}
|
||||
|
||||
// MessagesResponse is the response from fetching a conversation's messages.
|
||||
type MessagesResponse struct {
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Messages []Message
|
||||
}
|
||||
|
||||
// UnmarshalJSON handles the deeply nested HAL format for messages.
|
||||
func (r *MessagesResponse) UnmarshalJSON(data []byte) error {
|
||||
var raw struct {
|
||||
Embedded struct {
|
||||
Messages struct {
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Embedded struct {
|
||||
Message []Message `json:"mc:message"`
|
||||
} `json:"_embedded"`
|
||||
} `json:"mc:messages"`
|
||||
} `json:"_embedded"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Offset = raw.Embedded.Messages.Offset
|
||||
r.Limit = raw.Embedded.Messages.Limit
|
||||
r.TotalCount = raw.Embedded.Messages.TotalCount
|
||||
r.Messages = raw.Embedded.Messages.Embedded.Message
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerTime returns the current server datetime.
|
||||
func (s *MessagingService) ServerTime(ctx context.Context) (time.Time, error) {
|
||||
var resp struct {
|
||||
DateTime time.Time `json:"dateTime"`
|
||||
}
|
||||
if err := s.t.get(ctx, "/app/messaging/v1/datetime", nil, &resp); err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return resp.DateTime, nil
|
||||
}
|
||||
|
||||
// ConversationsOptions configures a conversations list request.
|
||||
type ConversationsOptions struct {
|
||||
Offset int
|
||||
Limit int
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
|
||||
// Conversations lists the authenticated user's conversations.
|
||||
func (s *MessagingService) Conversations(ctx context.Context, opts *ConversationsOptions) (*ConversationsResponse, error) {
|
||||
params := url.Values{}
|
||||
if opts != nil {
|
||||
if opts.Offset > 0 {
|
||||
params.Set("offset", fmt.Sprintf("%d", opts.Offset))
|
||||
}
|
||||
if opts.Limit > 0 {
|
||||
params.Set("limit", fmt.Sprintf("%d", opts.Limit))
|
||||
}
|
||||
if opts.Latitude != 0 {
|
||||
params.Set("latitude", fmt.Sprintf("%f", opts.Latitude))
|
||||
}
|
||||
if opts.Longitude != 0 {
|
||||
params.Set("longitude", fmt.Sprintf("%f", opts.Longitude))
|
||||
}
|
||||
}
|
||||
var resp ConversationsResponse
|
||||
if err := s.t.get(ctx, "/app/messaging/v1/conversations/android", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Messages retrieves the messages in a conversation.
|
||||
func (s *MessagingService) Messages(ctx context.Context, conversationID string, offset, limit int) (*MessagesResponse, error) {
|
||||
path := fmt.Sprintf("/app/messaging/v1/conversations/android/%s/messages", conversationID)
|
||||
params := url.Values{
|
||||
"offset": {fmt.Sprintf("%d", offset)},
|
||||
"limit": {fmt.Sprintf("%d", limit)},
|
||||
}
|
||||
var resp MessagesResponse
|
||||
if err := s.t.get(ctx, path, params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// StartConversation sends an initial message to the seller of a listing
|
||||
// using the ASQ (Ask Seller a Question) enquiry endpoint.
|
||||
// sellerID is the numeric seller ID from ListingDetail.SellerInformation.ID.
|
||||
// Returns nil on success (the API responds with 204 No Content).
|
||||
func (s *MessagingService) StartConversation(ctx context.Context, itemID, sellerID, text string) error {
|
||||
body := AskQuestionBody{
|
||||
AdURN: itemID,
|
||||
BidValue: -1.0,
|
||||
Body: text,
|
||||
RecipientID: sellerID,
|
||||
}
|
||||
return s.t.postJSON(ctx, "/app/enquiry/v1/question", nil, nil, body, nil)
|
||||
}
|
||||
|
||||
// SendMessage sends a text message in an existing conversation.
|
||||
func (s *MessagingService) SendMessage(ctx context.Context, conversationID, text string) (string, error) {
|
||||
path := fmt.Sprintf("/app/messaging/v1/conversations/android/%s/text", conversationID)
|
||||
body := struct {
|
||||
Text string `json:"text"`
|
||||
Actions any `json:"actions"`
|
||||
}{
|
||||
Text: text,
|
||||
}
|
||||
var resp struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := s.t.postJSON(ctx, path, nil, nil, body, &resp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.ID, nil
|
||||
}
|
||||
|
||||
// EmailSubscriptions returns the user's messaging email notification settings.
|
||||
func (s *MessagingService) EmailSubscriptions(ctx context.Context) (json.RawMessage, error) {
|
||||
var resp json.RawMessage
|
||||
if err := s.t.get(ctx, "/app/messaging/v1/subscriptions/email", nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user