diff --git a/package.json b/package.json index 5616cf0b..6498cbf0 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,8 @@ "cmdk": "^1.1.1", "cookies-next": "^6.0.0", "date-fns": "^4.1.0", - "etsy-ts": "^4.2.0", "discord-api-types": "^0.38.17", + "etsy-ts": "^5.0.0", "exa-js": "^1.8.8", "fast-deep-equal": "^3.1.3", "googleapis": "^150.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cafcd36..9c2330ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,12 +179,12 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - etsy-ts: - specifier: ^4.2.0 - version: 4.2.0 discord-api-types: specifier: ^0.38.17 version: 0.38.18 + etsy-ts: + specifier: ^5.0.0 + version: 5.0.0 exa-js: specifier: ^1.8.8 version: 1.8.8(encoding@0.1.13)(ws@8.18.3)(zod@3.25.56) @@ -3194,8 +3194,8 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - etsy-ts@4.2.0: - resolution: {integrity: sha512-YrVSiIP1s1FE7/isCnnkUR86te4+UABHfW7gWUsAHUDtPZj2NOw4VswabcfWxbtis+8pmZm7XQO0cQs8LZL+Fw==} + etsy-ts@5.0.0: + resolution: {integrity: sha512-Bh5Y3QJby9a22VOThJrIPBHEQDT7ixPK67m3EGiQUxRRywK1GWqbGAQi8E63yWGJKF41yO81E83JSBfJ/1ZypA==} event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} @@ -8670,11 +8670,11 @@ snapshots: etag@1.8.1: {} - etsy-ts@4.2.0: + etsy-ts@5.0.0: dependencies: axios: 1.7.7 axios-auth-refresh: 3.3.6(axios@1.7.7) - form-data: 4.0.3 + form-data: 4.0.4 tslib: 2.8.1 transitivePeerDependencies: - debug diff --git a/src/server/auth/custom-providers/etsy.ts b/src/server/auth/custom-providers/etsy.ts index 2358607b..20e3e3e2 100644 --- a/src/server/auth/custom-providers/etsy.ts +++ b/src/server/auth/custom-providers/etsy.ts @@ -10,7 +10,8 @@ export interface EtsyProfile { image_url_75x75?: string | null; } -export const etsyScopes = "email_r shops_r listings_r"; +export const etsyScopes = + "address_r address_w billing_r cart_r cart_w email_r favorites_r favorites_w feedback_r listings_d listings_r listings_w profile_r profile_w recommend_r recommend_w shops_r shops_w transactions_r transactions_w"; export default function EtsyProvider

( options: OAuthUserConfig

, diff --git a/src/toolkits/toolkits/etsy/base.ts b/src/toolkits/toolkits/etsy/base.ts index d585bf7d..31ee13ac 100644 --- a/src/toolkits/toolkits/etsy/base.ts +++ b/src/toolkits/toolkits/etsy/base.ts @@ -4,6 +4,8 @@ import { EtsyTools } from "./tools/tools"; import { getListings } from "@/toolkits/toolkits/etsy/tools/get-listings/base"; +import { createDraftListing } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/base"; + import type { ToolkitConfig } from "@/toolkits/types"; export const etsyParameters = z.object({}); @@ -14,6 +16,7 @@ export const baseEtsyToolkitConfig: ToolkitConfig< > = { tools: { [EtsyTools.getListings]: getListings, + [EtsyTools.createDraftListing]: createDraftListing, }, parameters: etsyParameters, }; diff --git a/src/toolkits/toolkits/etsy/client.tsx b/src/toolkits/toolkits/etsy/client.tsx index dc79b09b..1274e57f 100644 --- a/src/toolkits/toolkits/etsy/client.tsx +++ b/src/toolkits/toolkits/etsy/client.tsx @@ -10,6 +10,8 @@ import { createClientToolkit } from "@/toolkits/create-toolkit"; import { getListingsClientConfig } from "@/toolkits/toolkits/etsy/tools/get-listings/client"; +import { createDraftListingClientConfig } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/client"; + import { ToolkitGroups } from "@/toolkits/types"; import { EtsyTools } from "./tools/tools"; @@ -17,7 +19,8 @@ export const etsyClientToolkit = createClientToolkit( baseEtsyToolkitConfig, { name: "Etsy Toolkit", - description: "Etsy toolkit for fetching listing details.", + description: + "Etsy toolkit for Listing management, Payment management, Receipt management, Shipping management, Shop management and more!", icon: SiEtsy, form: null, type: ToolkitGroups.DataSource, @@ -37,5 +40,6 @@ export const etsyClientToolkit = createClientToolkit( }, { [EtsyTools.getListings]: getListingsClientConfig, + [EtsyTools.createDraftListing]: createDraftListingClientConfig, }, ); diff --git a/src/toolkits/toolkits/etsy/server.ts b/src/toolkits/toolkits/etsy/server.ts index a2cc94f5..9985fc3e 100644 --- a/src/toolkits/toolkits/etsy/server.ts +++ b/src/toolkits/toolkits/etsy/server.ts @@ -10,11 +10,13 @@ import { EtsyTools } from "./tools/tools"; import { EtsySecurityDataStorage } from "./security-data-storage"; import { getListingsServerConfig } from "@/toolkits/toolkits/etsy/tools/get-listings/server"; +import { createDraftListingServerConfig } from "@/toolkits/toolkits/etsy/tools/create-draft-listing/server"; export const etsyToolkitServer = createServerToolkit( baseEtsyToolkitConfig, "You have access to the Etsy toolkit for general account management. Currently, this toolkit provides:\n" + - "- **Get Listings**: Retrieves all listings and their image URLs associated with the shop associated with the signed-in user.\n\n", + "- **Get Listings By Shop**: Retrieves listings associated with the shop owned by authenticated user. Has the ability to fetch associations relating to each listing as well.\n" + + "- **Create Draft Listing**: Creates a new draft listing in the shop owned by authenticated user. Accepts a variety of inputs to assign to listing.\n", async () => { const account = await api.accounts.getAccountByProvider("etsy"); @@ -32,7 +34,14 @@ export const etsyToolkitServer = createServerToolkit( }); return { - [EtsyTools.getListings]: getListingsServerConfig(etsy), + [EtsyTools.getListings]: getListingsServerConfig( + etsy, + account.providerAccountId, + ), + [EtsyTools.createDraftListing]: createDraftListingServerConfig( + etsy, + account.providerAccountId, + ), }; }, ); diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts b/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts new file mode 100644 index 00000000..603e2866 --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/base.ts @@ -0,0 +1,299 @@ +import { z } from "zod"; +import { createBaseTool } from "@/toolkits/create-tool"; +import type { IShopListing } from "etsy-ts"; + +export const createDraftListing = createBaseTool({ + description: + "Creates a new draft listing in the Etsy shop associated with the authenticated user." + + "Requires input: quantity, title, description, price, who_made, when_made, is_supply, and taxonomy_id." + + "Optional inputs include: (if not specified, the default value is null" + + "shipping_profile_id — ID of the shipping profile to associate with the listing. REQUIRED if listing type is physical." + + "return_policy_id - the numeric ID of the Return Policy." + + "materials - A list of material strings for materials used in the product. Valid materials strings contain only letters, numbers, and whitespace characters. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}]/u)." + + "shop_section_id - The numeric ID of the shop section for this listing." + + "processing_min - The minimum number of days it takes to produce the item." + + "processing_max - The maximum number of days it takes to produce the item." + + "readiness_state_id - The numeric ID of the processing profile associated with the listing. Required when type is physical." + + "tags - A comma-separated list of tag strings for the listing. When creating or updating a listing, valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}-'™©®]/u)." + + 'styles - An array of style strings for this listing, each of which is free-form text string such as "Formal", or "Steampunk". When creating or updating a listing, the listing may have up to two styles. Valid style strings contain only letters, numbers, and whitespace characters. (regex: /[^\\p{L}\\p{Nd}\\p{Zs}]/u).' + + "item_weight - The numeric weight of the product measured in units set in 'item_weight_unit'. If set, the values must be greater than 0." + + "item_length - The numeric length of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_width - The numeric width of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_height - The numeric height of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0." + + "item_weight_unit - The unit of measurement for the weight of the product. Valid values are 'g' (grams), 'kg' (kilograms), 'oz' (ounces), and 'lb' (pounds). Required if 'item_weight' is provided." + + "item_dimensions_unit - The unit of measurement for the dimensions of the product. Valid values are 'mm' (millimeters), 'cm' (centimeters), 'm' (meters), 'in' (inches), and 'ft' (feet). Required if any of 'item_length', 'item_width', or 'item_height' is provided." + + "is_personalizable - When ture, indicates that the listing is personalizable." + + "personalization_is_required - When true, indicates that personalization is required for the listing. Will only change if _is_personalizable is true" + + "personalization_char_count_max - The maximum number of characters allowed for personalization. Will only change if _is_personalizable is true." + + "personalization_instructions - Instructions for personalization. Will only change if _is_personalizable is true." + + "production_partner_ids - An array of unique IDs of production partner ids." + + "image_ids - An array of numeric image IDs of the images in a listing, which can include up to 10 images." + + "is_customizable - When true, a buyer may contact the seller for a customized order. The default value is true when a shop accepts custom orders. Does not apply to shops that do not accept custom orders." + + "should_auto_renew - When true, renews a listing for four months upon expiration." + + "is_taxable - When true, applicable shop tax rates apply to this listing at checkout" + + "type - An enumerated type string that indicates whether the listing is physical or a digital download or both.", + inputSchema: z.object({ + title: z + .string() + .min(1) + .max(140) + .describe( + "The title of the listing. Can only contain letters, numbers, punctuation marks, mathematical symbols, whitespace characters", + ), + description: z + .string() + .min(1) + .max(1000) + .describe("The description of the listing."), + price: z + .number() + .positive() + .describe( + "The price of the item. Must be a positive value with up to two decimal places.", + ), + quantity: z + .number() + .min(1) + .describe( + "The quantity of items available in this listing. Must be at least 1.", + ), + who_made: z + .enum(["i_did", "collective", "someone_else"]) + .describe("Who made the item."), + when_made: z + .enum([ + "made_to_order", + "2020_2025", + "2010_2019", + "2006_2009", + "before_2006", + "2000_2005", + "1990s", + "1980s", + "1970s", + "1960s", + "1950s", + "1940s", + "1930s", + "1920s", + "1910s", + "1900s", + "1800s", + "1700s", + "before_1700", + ]) + .describe( + "An enumerated string for the era in which the maker made the product in this listing. Helps buyers locate the listing under the Vintage heading.", + ), + taxonomy_id: z + .number() + .int() + .positive() + .describe("The taxonomy ID for the category of the listing."), + is_supply: z + .boolean() + .describe( + "When true, tags the listing as a supply product, else indicates that it's a finished product.", + ), + shipping_profile_id: z + .number() + .int() + .positive() + .optional() + .describe( + "ID of the shipping profile to associate with the listing. REQUIRED if listing type is physical.", + ), + return_policy_id: z + .number() + .int() + .positive() + .optional() + .describe("The numeric ID of the Return Policy."), + materials: z + .array( + z + .string() + .regex(/[^\p{L}\p{Nd}\p{Zs}]/u, { + message: + "Valid materials strings contain only letters, numbers, and whitespace characters.", + }) + .min(1) + .max(25) + .describe( + "A material string for materials used in the product. Valid materials strings contain only letters, numbers, and whitespace characters.", + ), + ) + .max(25), + shop_section_id: z + .number() + .int() + .positive() + .optional() + .describe("The numeric ID of the shop section for this listing."), + processing_min: z + .number() + .int() + .min(0) + .optional() + .describe("The minimum number of days it takes to produce the item."), + processing_max: z + .number() + .int() + .min(0) + .optional() + .describe("The maximum number of days it takes to produce the item."), + readiness_state_id: z + .number() + .int() + .positive() + .optional() + .describe( + "The numeric ID of the processing profile associated with the listing. Required when type is physical.", + ), + tags: z + .array( + z + .string() + .regex(/^[\p{L}\p{Nd}\p{Zs}'™©®-]+$/u, { + message: + "Valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®.", + }) + .min(1) + .max(25) + .describe( + "A tag string for the listing. When creating or updating a listing, valid tag strings contain only letters, numbers, whitespace characters, -, ', ™, ©, and ®.", + ), + ) + .max(13) + .describe("A list of tag strings for the listing."), + styles: z + .array( + z + .string() + .regex(/[^\p{L}\p{Nd}\p{Zs}]/u, { + message: + "Valid style strings contain only letters, numbers, and whitespace characters.", + }) + .min(1) + .max(25) + .describe( + 'A style string for this listing, such as "Formal", or "Steampunk". When creating or updating a listing, the listing may have up to two styles. Valid style strings contain only letters, numbers, and whitespace characters.', + ), + ) + .max(2) + .describe("An array of style strings for this listing."), + item_weight: z + .number() + .positive() + .optional() + .describe( + "The numeric weight of the product measured in units set in 'item_weight_unit'. If set, the values must be greater than 0.", + ), + item_length: z + .number() + .positive() + .optional() + .describe( + "The numeric length of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_width: z + .number() + .positive() + .optional() + .describe( + "The numeric width of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_height: z + .number() + .positive() + .optional() + .describe( + "The numeric height of the product measured in units set in 'item_dimensions_unit'. If set, the values must be greater than 0.", + ), + item_weight_unit: z + .enum(["g", "kg", "oz", "lb"]) + .optional() + .describe( + "The unit of measurement for the weight of the product. Valid values are 'g' (grams), 'kg' (kilograms), 'oz' (ounces), and 'lb' (pounds). Required if 'item_weight' is provided.", + ), + item_dimensions_unit: z + .enum(["mm", "cm", "m", "in", "ft"]) + .optional() + .describe( + "The unit of measurement for the dimensions of the product. Valid values are 'mm' (millimeters), 'cm' (centimeters), 'm' (meters), 'in' (inches), and 'ft' (feet). Required if any of 'item_length', 'item_width', or 'item_height' is provided.", + ), + is_personalizable: z + .boolean() + .optional() + .describe("When true, indicates that the listing is personalizable."), + personalization_is_required: z + .boolean() + .optional() + .describe( + "When true, indicates that personalization is required for the listing. Will only change if _is_personalizable is true", + ), + personalization_char_count_max: z + .number() + .int() + .min(1) + .max(250) + .optional() + .describe( + "The maximum number of characters allowed for personalization. Will only change if _is_personalizable is true.", + ), + personalization_instructions: z + .string() + .max(500) + .optional() + .describe( + "Instructions for personalization. Will only change if _is_personalizable is true.", + ), + production_partner_ids: z + .array( + z + .number() + .int() + .positive() + .describe("A unique ID of a production partner."), + ) + .optional() + .describe("An array of unique IDs of production partner ids."), + image_ids: z + .array( + z + .number() + .int() + .positive() + .describe("A numeric image ID of an image in a listing."), + ) + .max(10) + .optional() + .describe( + "An array of numeric image IDs of the images in a listing, which can include up to 10 images.", + ), + is_customizable: z + .boolean() + .optional() + .describe( + "When true, a buyer may contact the seller for a customized order. The default value is true when a shop accepts custom orders. Does not apply to shops that do not accept custom orders.", + ), + should_auto_renew: z + .boolean() + .optional() + .describe("When true, renews a listing for four months upon expiration."), + is_taxable: z + .boolean() + .optional() + .describe( + "When true, applicable shop tax rates apply to this listing at checkout", + ), + type: z + .enum(["physical", "download", "both"]) + .describe( + "An enumerated type string that indicates whether the listing is physical or a digital download or both.", + ), + }), + outputSchema: z.object({ + result: z.custom(), + }), +}); diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx b/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx new file mode 100644 index 00000000..92fe60d4 --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/client.tsx @@ -0,0 +1,21 @@ +import { Search } from "lucide-react"; +import type { ClientToolConfig } from "@/toolkits/types"; +import type { createDraftListing } from "./base"; + +export const createDraftListingClientConfig: ClientToolConfig< + typeof createDraftListing.inputSchema.shape, + typeof createDraftListing.outputSchema.shape +> = { + CallComponent: ({ isPartial }) => ( +

+ + {isPartial && ...} +
+ ), + ResultComponent: ({ result: { result } }) => ( +
+

Listing

+
{result.title}
) +
+ ), +}; diff --git a/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts b/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts new file mode 100644 index 00000000..30097e60 --- /dev/null +++ b/src/toolkits/toolkits/etsy/tools/create-draft-listing/server.ts @@ -0,0 +1,123 @@ +import type { Etsy, ICreateDraftListingPayload } from "etsy-ts"; + +import type { ServerToolConfig } from "@/toolkits/types"; +import type { createDraftListing } from "./base"; + +export const createDraftListingServerConfig = ( + etsy: Etsy, + userId: string, +): ServerToolConfig< + typeof createDraftListing.inputSchema.shape, + typeof createDraftListing.outputSchema.shape +> => { + return { + callback: async ({ + title, + description, + price, + quantity, + who_made, + is_supply, + when_made, + taxonomy_id, + shipping_profile_id, + shop_section_id, + materials, + return_policy_id, + processing_min, + processing_max, + readiness_state_id, + tags, + styles, + item_weight, + item_weight_unit, + item_length, + item_width, + item_height, + item_dimensions_unit, + is_personalizable, + personalization_is_required, + personalization_char_count_max, + personalization_instructions, + production_partner_ids, + image_ids, + is_customizable, + should_auto_renew, + is_taxable, + type, + }) => { + try { + const shop = await etsy.Shop.getShopByOwnerUserId(Number(userId)); + + const shopId = shop.data.shop_id; + + if (!shopId) throw new Error("Missing Etsy shop ID"); + + const params: ICreateDraftListingPayload = { + title, + description, + price, + quantity, + who_made, + is_supply, + when_made, + taxonomy_id, + materials, + type, + styles, + ...(shipping_profile_id !== undefined ? { shipping_profile_id } : {}), + ...(shop_section_id !== undefined ? { shop_section_id } : {}), + ...(return_policy_id !== undefined ? { return_policy_id } : {}), + ...(processing_min !== undefined ? { processing_min } : {}), + ...(processing_max !== undefined ? { processing_max } : {}), + ...(readiness_state_id !== undefined ? { readiness_state_id } : {}), + ...(item_weight !== undefined ? { item_weight } : {}), + ...(item_weight_unit !== undefined ? { item_weight_unit } : {}), + ...(item_length !== undefined ? { item_length } : {}), + ...(item_width !== undefined ? { item_width } : {}), + ...(item_height !== undefined ? { item_height } : {}), + ...(item_dimensions_unit !== undefined + ? { item_dimensions_unit } + : {}), + ...(is_personalizable !== undefined ? { is_personalizable } : {}), + ...(personalization_is_required !== undefined + ? { personalization_is_required } + : {}), + ...(personalization_char_count_max !== undefined + ? { personalization_char_count_max } + : {}), + ...(personalization_instructions !== undefined + ? { personalization_instructions } + : {}), + ...(production_partner_ids !== undefined + ? { production_partner_ids } + : {}), + ...(image_ids !== undefined ? { image_ids } : {}), + ...(is_customizable !== undefined ? { is_customizable } : {}), + ...(should_auto_renew !== undefined ? { should_auto_renew } : {}), + ...(is_taxable !== undefined ? { is_taxable } : {}), + ...(tags !== undefined ? { tags } : {}), + ...(type !== undefined ? { type } : {}), + }; + + const listing = await etsy.ShopListing.createDraftListing( + { shopId }, + params, + undefined, + ); + + if (!listing.data) throw new Error("Missing Etsy listing"); + + return { + result: listing.data, + }; + } catch (error) { + console.error("Etsy API error:", error); + throw new Error("Failed to create draft listing on Etsy"); + } + }, + message: + "Successfully created the Etsy draft listing. The user is shown the responses in the UI. Do not reiterate them. " + + "If you called this tool because the user asked a question, answer the question.", + }; +}; diff --git a/src/toolkits/toolkits/etsy/tools/get-listings/base.ts b/src/toolkits/toolkits/etsy/tools/get-listings/base.ts index 74dc6515..b67c9734 100644 --- a/src/toolkits/toolkits/etsy/tools/get-listings/base.ts +++ b/src/toolkits/toolkits/etsy/tools/get-listings/base.ts @@ -4,8 +4,55 @@ import type { IShopListing } from "etsy-ts"; export const getListings = createBaseTool({ description: - "Fetches all listings from the Etsy shop associated with the authenticated user.", - inputSchema: z.object({}), + "Fetches all listings from the Etsy shop associated with the authenticated user." + + "No additional input is required. But there are optional ones:" + + "limit — page size (default 25; max 100)" + + "offset — number of results to skip (use for pagination)" + + "sort_on — field to sort by (e.g., created, updated, price, score). Note: some sorts only work when combined with a search option; score is always descending regardless of sort_order." + + "sort_order — up (ascending) or down (descending), when supported by the chosen sort_on." + + "includes - An enumerated string that attaches a valid association. Acceptable inputs are 'Shipping', 'Shop', 'Images', 'User', 'Translations' and 'Inventory'.", + inputSchema: z.object({ + limit: z + .number() + .min(1) + .max(100) + .optional() + .describe( + "Maximum number of items to return. Default: 25. Min: 1. Max: 100", + ), + sort_on: z + .enum(["created", "updated", "price", "score"]) + .optional() + .describe( + "Field to sort by (e.g., created, updated, price, score). Note: some sorts only work when combined with a search option; score is always descending regardless of sort_order. Default: created", + ), + sort_order: z + .enum(["asc", "desc", "up", "down"]) + .optional() + .describe( + "Sort order: up (ascending) or down (descending). Default: desc", + ), + offset: z + .number() + .min(0) + .optional() + .describe("Number of results to skip (use for pagination). Default: 0"), + includes: z + .enum([ + "Shipping", + "Images", + "Shop", + "User", + "Translations", + "Inventory", + "Videos", + ]) + .array() + .optional() + .describe( + "An enumerated string that attaches a valid association. Acceptable inputs are 'Shipping', 'Shop', 'Images', 'User', 'Translations' and 'Inventory'.", + ), + }), outputSchema: z.object({ results: z.array(z.custom()), }), diff --git a/src/toolkits/toolkits/etsy/tools/get-listings/server.ts b/src/toolkits/toolkits/etsy/tools/get-listings/server.ts index f6e11259..77c66250 100644 --- a/src/toolkits/toolkits/etsy/tools/get-listings/server.ts +++ b/src/toolkits/toolkits/etsy/tools/get-listings/server.ts @@ -1,32 +1,34 @@ -import type { Etsy } from "etsy-ts"; +import type { Etsy, IGetListingsByShopParams } from "etsy-ts"; import type { ServerToolConfig } from "@/toolkits/types"; import type { getListings } from "./base"; export const getListingsServerConfig = ( etsy: Etsy, + userId: string, ): ServerToolConfig< typeof getListings.inputSchema.shape, typeof getListings.outputSchema.shape > => { return { - callback: async () => { + callback: async ({ limit, offset, sort_on, sort_order, includes }) => { try { - const user = await etsy.User.getMe(); - - const userId = user.data.user_id; - - if (!userId) throw new Error("Missing Etsy user ID"); - - const shop = await etsy.Shop.getShopByOwnerUserId(userId); + const shop = await etsy.Shop.getShopByOwnerUserId(Number(userId)); const shopId = shop.data.shop_id; if (!shopId) throw new Error("Missing Etsy shop ID"); - const listings = await etsy.ShopListing.getFeaturedListingsByShop({ + const params: IGetListingsByShopParams = { shopId, - }); + ...(limit !== undefined ? { limit } : {}), + ...(offset !== undefined ? { offset } : {}), + ...(sort_on !== undefined ? { sort_on } : {}), + ...(sort_order !== undefined ? { sort_order } : {}), + ...(includes !== undefined ? { includes } : {}), + }; + + const listings = await etsy.ShopListing.getListingsByShop(params); if (!listings.data.results) throw new Error("Missing Etsy listings"); diff --git a/src/toolkits/toolkits/etsy/tools/tools.ts b/src/toolkits/toolkits/etsy/tools/tools.ts index 8af37ff8..f8724452 100644 --- a/src/toolkits/toolkits/etsy/tools/tools.ts +++ b/src/toolkits/toolkits/etsy/tools/tools.ts @@ -1,3 +1,4 @@ export enum EtsyTools { getListings = "get-listings", + createDraftListing = "create-draft-listing", }