{"record":{"id":"463439f909b72508","repo":"jackwener/OpenCLI","slug":"1688-item-expects-an-offer-url-or-offer-id","errorCode":null,"errorMessage":"1688 item expects an offer URL or offer ID","messagePattern":"1688 item expects an offer URL or offer ID","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/1688/shared.js","lineNumber":138,"sourceCode":"}\nexport function parseSearchLimit(input) {\n    const parsed = Number.parseInt(String(input ?? SEARCH_LIMIT_DEFAULT), 10);\n    if (!Number.isFinite(parsed) || parsed < 1) {\n        throw new ArgumentError('1688 search --limit must be a positive integer', 'Example: opencli 1688 search \"桌面置物架\" --limit 20');\n    }\n    return Math.min(SEARCH_LIMIT_MAX, parsed);\n}\nexport function buildSearchUrl(query) {\n    const normalized = cleanText(query);\n    if (!normalized) {\n        throw new ArgumentError('1688 search query cannot be empty', 'Example: opencli 1688 search \"桌面置物架\" --limit 20');\n    }\n    return `${SEARCH_URL_PREFIX}${encodeURIComponent(normalized)}`;\n}\nexport function buildDetailUrl(input) {\n    const offerId = extractOfferId(input);\n    if (!offerId) {\n        throw new ArgumentError('1688 item expects an offer URL or offer ID', 'Example: opencli 1688 item 887904326744');\n    }\n    return `${DETAIL_URL_PREFIX}${offerId}.html`;\n}\nexport function resolveStoreUrl(input) {\n    const normalized = cleanText(input);\n    if (!normalized) {\n        throw new ArgumentError('1688 store expects a store URL or member ID', 'Example: opencli 1688 store https://yinuoweierfushi.1688.com/');\n    }\n    const memberId = extractMemberId(normalized);\n    if (memberId) {\n        return `${STORE_MOBILE_URL_PREFIX}${memberId}`;\n    }\n    if (/^https?:\\/\\//i.test(normalized)) {\n        return canonicalizeStoreUrl(normalized);\n    }\n    if (normalized.endsWith('.1688.com')) {\n        return canonicalizeStoreUrl(`https://${normalized}`);\n    }","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/1688/shared.js#L120-L156","documentation":"buildDetailUrl constructs a 1688 offer detail URL from an arbitrary input string. It first runs extractOfferId to pull an offer ID out of a URL or bare ID; when the input yields no offer ID it throws ArgumentError, so callers never build a malformed detail URL. The message tells the user what kind of value is expected, and the example fix shows the canonical CLI invocation.","triggerScenarios":"Calling buildDetailUrl (via itemUrl/detailUrl) with an empty string, a 1688 search URL, a non-1688 URL, a URL without an offer/numeric ID segment, or a typo'd numeric ID containing letters/punctuation that extractOfferId cannot parse.","commonSituations":"Users paste a search results page or homepage instead of a product (offer) page; IDs copied from rendered pages include whitespace or punctuation; downstream systems pass null/undefined because an earlier scrape step failed; mixing up store URLs (subdomain.1688.com) with offer URLs.","solutions":["Pass a valid offer detail URL like https://detail.1688.com/offer/887904326744.html or the bare numeric offer ID (e.g. 887904326744).","Strip whitespace/extra text and re-copy the offer ID from the browser address bar of the product page.","Verify the input is a detail.1688.com offer URL, not a search/store page; if you only have a store URL use the store command instead of item."],"exampleFix":"// before\nopencli 1688 item \"https://www.1688.com\" // no offer ID -> ArgumentError\n// after\nopencli 1688 item 887904326744\n// or\nopencli 1688 item https://detail.1688.com/offer/887904326744.html","handlingStrategy":"validation","validationCode":"const OFFER_ID_RE = /(\\d{8,})/;\nfunction hasOfferId(input) {\n  if (typeof input !== 'string' || !input.trim()) return false;\n  return /detail\\.1688\\.com\\/offer\\/\\d+\\.html/.test(input) || OFFER_ID_RE.test(input);\n}\nif (!hasOfferId(userInput)) throw new Error(`Not a 1688 offer URL/ID: ${userInput}`);","typeGuard":"function isOfferUrl(input) {\n  return typeof input === 'string' && /^https:\\/\\/detail\\.1688\\.com\\/offer\\/\\d+\\.html$/.test(input.trim());\n}\nfunction isBareOfferId(input) {\n  return typeof input === 'string' && /^\\d{8,}$/.test(input.trim());\n}","tryCatchPattern":"try {\n  const url = buildDetailUrl(input);\n} catch (e) {\n  if (e.name === 'ArgumentError') {\n    console.error(`Bad 1688 item input: ${input}. Provide an offer URL or numeric offer ID.`);\n  } else throw e;\n}","preventionTips":["Normalize input with trim() before passing.","Prefer pasting the full detail.1688.com/offer/<id>.html URL over hand-typed IDs.","Unit-test buildDetailUrl with search URLs and store URLs to confirm they are rejected early.","Never pass null/undefined from upstream steps — assert input exists first."],"tags":["argument-validation","url-parsing","input-error"],"backgroundTag":"invalid-input-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}