{"record":{"id":"4c34d06873c59055","repo":"jackwener/OpenCLI","slug":"twitter-download-username-must-be-a-valid-twitter","errorCode":null,"errorMessage":"twitter download username must be a valid Twitter/X handle","messagePattern":"twitter download username must be a valid Twitter/X handle","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/download.js","lineNumber":334,"sourceCode":"    columns: ['index', 'tweet_id', 'url', 'type', 'status', 'size'],\n    func: async (page, kwargs) => {\n        try {\n            const rawUsername = String(kwargs.username ?? '').trim();\n            const tweetUrl = String(kwargs['tweet-url'] ?? '').trim();\n            const output = kwargs.output;\n            if (!rawUsername && !tweetUrl) {\n                throw new ArgumentError('twitter download requires either <username> or --tweet-url');\n            }\n            if (rawUsername && tweetUrl) {\n                throw new ArgumentError('Use either <username> or --tweet-url, not both');\n            }\n            if (tweetUrl) {\n                return downloadSingleTweet(page, tweetUrl, output);\n            }\n            const limit = requireLimit(kwargs.limit);\n            const username = normalizeTwitterScreenName(rawUsername);\n            if (!username) {\n                throw new ArgumentError('twitter download username must be a valid Twitter/X handle', 'Example: opencli twitter download @jack --limit 20');\n            }\n            return downloadUserMedia(page, username, limit, output);\n        }\n        catch (err) {\n            if (err instanceof CliError) throw err;\n            throw new CommandExecutionError(`twitter download failed: ${err?.message ?? String(err)}`);\n        }\n    },\n});\n\nasync function downloadUserMedia(page, username, limit, output) {\n    await page.goto(`https://x.com/${username}`);\n    await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\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) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/download.js#L316-L352","documentation":"After --tweet-url mode is ruled out, the positional username is passed through normalizeTwitterScreenName, which strips @/URL decorations and validates the handle. If nothing valid remains (empty string), the command throws an ArgumentError indicating the handle is invalid, with an example usage hint as remediation detail.","triggerScenarios":"Running `opencli twitter download ''`, passing only whitespace, or a value that normalizeTwitterScreenName cannot reduce to a valid screen name (e.g. a full profile URL with no resolvable handle or garbage characters).","commonSituations":"Forgetting the positional argument entirely; quoting issues in shell producing an empty string; pasting a full x.com profile URL where the extractor yields nothing; variables interpolated empty in scripts.","solutions":["Provide a valid handle, e.g. `opencli twitter download @jack --limit 20`","Check that the shell variable holding the username is not empty before invoking","If using a profile URL, reduce it to the handle yourself (x.com/jack -> @jack)"],"exampleFix":"// before\nopencli twitter download \"\"\n// after\nopencli twitter download @jack --limit 20","handlingStrategy":"validation","validationCode":"const handle = raw.replace(/^@/, '');\nif (!/^[A-Za-z0-9_]{1,15}$/.test(handle)) {\n  throw new Error(`Invalid Twitter handle: ${raw}`);\n}","typeGuard":"const isValidHandle = (v) => typeof v === 'string' && /^[A-Za-z0-9_]{1,15}$/.test(v.replace(/^@/, ''));","tryCatchPattern":null,"preventionTips":["Validate the handle matches /^[A-Za-z0-9_]{1,15}$/ before invoking","Strip @ and URL prefixes yourself when accepting input from users","Check shell variables are non-empty before interpolation"],"tags":["cli","argument-validation","twitter"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}