{"record":{"id":"5f977be55a55f7e1","repo":"jackwener/OpenCLI","slug":"twitter-likes-username-must-be-a-valid-twitter-x-h","errorCode":null,"errorMessage":"twitter likes username must be a valid Twitter/X handle","messagePattern":"twitter likes username must be a valid Twitter/X handle","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/likes.js","lineNumber":226,"sourceCode":"        const useOutputFile = Boolean(fetchAll && outputFile);\n        const maxPages = resolveMaxPages(kwargs, fetchAll);\n        const topByEngagement = Number(kwargs['top-by-engagement'] || 0);\n        if (useOutputFile && topByEngagement > 0) {\n            throw new ArgumentError('--top-by-engagement cannot be combined with --output-file');\n        }\n        if (outputFile && !fetchAll) {\n            throw new ArgumentError('--output-file requires --all');\n        }\n        if (resumeFile && !fetchAll) {\n            throw new ArgumentError('--resume-file requires --all');\n        }\n        if (outputFile && !resumeFile) {\n            throw new ArgumentError('--output-file requires --resume-file so partial archives remain resumable');\n        }\n        const rawUsername = String(kwargs.username ?? '').trim();\n        let username = normalizeTwitterScreenName(rawUsername);\n        if (rawUsername && !username) {\n            throw new ArgumentError('twitter likes username must be a valid Twitter/X handle', 'Example: opencli twitter likes @jack --limit 20');\n        }\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0)\n            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        // If no username provided, detect the logged-in user.\n        // Bridge wraps primitive page.evaluate returns as { session, data:<value> };\n        // unwrap so the href string is usable downstream.\n        if (!username) {\n            // Force a navigation to the home surface so the AppTabBar sidebar\n            // is rendered; the framework pre-nav lands on bare x.com which\n            // does not always expose AppTabBar_Profile_Link.\n            await page.goto('https://x.com/home');\n            await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\n            const href = unwrapBrowserResult(await page.evaluate(`() => {\n        const link = document.querySelector('a[data-testid=\"AppTabBar_Profile_Link\"]');\n        return link ? link.getAttribute('href') : null;\n      }`));","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/likes.js#L208-L244","documentation":"The username argument is passed through normalizeTwitterScreenName, which strips/validates Twitter/X handle syntax. If a non-empty raw username yields no valid normalized handle, the value is not a legal screen name and ArgumentError is thrown, with an example command as remediation text.","triggerScenarios":"Passing something like `opencli twitter likes 'jack dorsey'` or `opencli twitter likes 'https://x.com/jack'` or a handle with invalid characters (!, spaces, >15 chars) as the positional username.","commonSituations":"Pasting a full profile URL instead of the handle; including trailing punctuation from copy-paste; shell quoting split the handle so the flag got a wrong value; typos like 'ja ck'.","solutions":["Pass just the handle: `opencli twitter likes @jack --limit 20` (the @ prefix and alphanumerics/underscore, max 15 chars).","Strip surrounding quotes/URL parts — extract the screen name from any profile URL first.","Quote the argument in your shell so spaces don't split it, and check `set -x` output if scripting."],"exampleFix":"// before\nopencli twitter likes 'https://x.com/jack?foo=1'\n// after\nopencli twitter likes @jack --limit 20","handlingStrategy":"validation","validationCode":"function isValidHandle(raw) {\n  const h = raw.replace(/^@/, '');\n  return /^[A-Za-z0-9_]{1,15}$/.test(h);\n}\nif (!isValidHandle(process.argv[3])) throw new Error('Pass a valid handle, e.g. @jack');","typeGuard":"function isTwitterScreenName(v) {\n  return typeof v === 'string' && /^@?[A-Za-z0-9_]{1,15}$/.test(v);\n}","tryCatchPattern":"try {\n  await cli({ username: raw });\n} catch (err) {\n  if (err instanceof ArgumentError && /valid Twitter\\/X handle/.test(err.message)) {\n    // extract handle from URL or fix quoting, then retry\n  }\n}","preventionTips":["Strip URLs to the screen name before passing the positional argument.","Quote the username in shell scripts to avoid word splitting.","Validate handles client-side with /^[A-Za-z0-9_]{1,15}$/ (optional leading @)."],"tags":["cli","argument-validation","twitter","username"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}