test(cli): validate mentions handle
This commit is contained in:
@@ -27,6 +27,7 @@ bird replies 1234567890123456789
|
|||||||
# Search + mentions
|
# Search + mentions
|
||||||
bird search "from:steipete" -n 5
|
bird search "from:steipete" -n 5
|
||||||
bird mentions -n 5
|
bird mentions -n 5
|
||||||
|
bird mentions --user @steipete -n 5
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
@@ -38,7 +39,7 @@ bird mentions -n 5
|
|||||||
- `bird replies <tweet-id-or-url> [--json]` — list replies to a tweet.
|
- `bird replies <tweet-id-or-url> [--json]` — list replies to a tweet.
|
||||||
- `bird thread <tweet-id-or-url> [--json]` — show the full conversation thread.
|
- `bird thread <tweet-id-or-url> [--json]` — show the full conversation thread.
|
||||||
- `bird search "<query>" [-n count] [--json]` — search for tweets matching a query.
|
- `bird search "<query>" [-n count] [--json]` — search for tweets matching a query.
|
||||||
- `bird mentions [-n count] [--json]` — find tweets mentioning @clawdbot.
|
- `bird mentions [-n count] [--user @handle] [--json]` — find tweets mentioning a user (defaults to the authenticated user).
|
||||||
- `bird whoami` — print which Twitter account your cookies belong to.
|
- `bird whoami` — print which Twitter account your cookies belong to.
|
||||||
- `bird check` — show which credentials are available and where they were sourced from.
|
- `bird check` — show which credentials are available and where they were sourced from.
|
||||||
|
|
||||||
@@ -92,6 +93,10 @@ Environment shortcuts:
|
|||||||
- `--json` prints raw tweet objects for read/replies/thread/search/mentions.
|
- `--json` prints raw tweet objects for read/replies/thread/search/mentions.
|
||||||
- `read` returns full text for Notes and Articles when present.
|
- `read` returns full text for Notes and Articles when present.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
`bird --version` prints `package.json` version plus current git sha when available, e.g. `0.1.1 (3df7969b)`.
|
||||||
|
|
||||||
## Media uploads (Sweetistics only)
|
## Media uploads (Sweetistics only)
|
||||||
|
|
||||||
- Attach media with `--media` (repeatable) and optional `--alt` per item.
|
- Attach media with `--media` (repeatable) and optional `--alt` per item.
|
||||||
|
|||||||
+8
-3
@@ -19,7 +19,7 @@ import { resolveCliInvocation } from './lib/cli-args.js';
|
|||||||
import { resolveCredentials } from './lib/cookies.js';
|
import { resolveCredentials } from './lib/cookies.js';
|
||||||
import { type EngineMode, resolveEngineMode, shouldUseSweetistics } from './lib/engine.js';
|
import { type EngineMode, resolveEngineMode, shouldUseSweetistics } from './lib/engine.js';
|
||||||
import { extractTweetId } from './lib/extract-tweet-id.js';
|
import { extractTweetId } from './lib/extract-tweet-id.js';
|
||||||
import { normalizeHandle } from './lib/normalize-handle.js';
|
import { mentionsQueryFromUserOption, normalizeHandle } from './lib/normalize-handle.js';
|
||||||
import { SweetisticsClient } from './lib/sweetistics-client.js';
|
import { SweetisticsClient } from './lib/sweetistics-client.js';
|
||||||
import { type TweetData, TwitterClient } from './lib/twitter-client.js';
|
import { type TweetData, TwitterClient } from './lib/twitter-client.js';
|
||||||
import { getCliVersion } from './lib/version.js';
|
import { getCliVersion } from './lib/version.js';
|
||||||
@@ -817,8 +817,13 @@ program
|
|||||||
const engine = resolveEngineMode(opts.engine);
|
const engine = resolveEngineMode(opts.engine);
|
||||||
const useSweetistics = shouldUseSweetistics(engine, Boolean(sweetistics.apiKey));
|
const useSweetistics = shouldUseSweetistics(engine, Boolean(sweetistics.apiKey));
|
||||||
|
|
||||||
const explicitHandle = normalizeHandle(cmdOpts.user);
|
const fromUserOpt = mentionsQueryFromUserOption(cmdOpts.user);
|
||||||
let query: string | null = explicitHandle ? `@${explicitHandle}` : null;
|
if (fromUserOpt.error) {
|
||||||
|
console.error(`❌ ${fromUserOpt.error}`);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query: string | null = fromUserOpt.query;
|
||||||
|
|
||||||
if (useSweetistics) {
|
if (useSweetistics) {
|
||||||
if (!sweetistics.apiKey) {
|
if (!sweetistics.apiKey) {
|
||||||
|
|||||||
@@ -11,3 +11,20 @@ export function normalizeHandle(input?: string | null): string | null {
|
|||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mentionsQueryFromUserOption(userOption?: string | null): {
|
||||||
|
query: string | null;
|
||||||
|
error: string | null;
|
||||||
|
} {
|
||||||
|
if (typeof userOption === 'undefined') return { query: null, error: null };
|
||||||
|
|
||||||
|
const handle = normalizeHandle(userOption);
|
||||||
|
if (!handle) {
|
||||||
|
return {
|
||||||
|
query: null,
|
||||||
|
error: 'Invalid --user handle. Expected something like @steipete (letters, digits, underscore; max 15).',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { query: `@${handle}`, error: null };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { normalizeHandle } from '../src/lib/normalize-handle.js';
|
import { mentionsQueryFromUserOption, normalizeHandle } from '../src/lib/normalize-handle.js';
|
||||||
|
|
||||||
describe('normalizeHandle', () => {
|
describe('normalizeHandle', () => {
|
||||||
it('accepts bare handle', () => {
|
it('accepts bare handle', () => {
|
||||||
@@ -22,3 +22,20 @@ describe('normalizeHandle', () => {
|
|||||||
expect(normalizeHandle('a'.repeat(16))).toBeNull();
|
expect(normalizeHandle('a'.repeat(16))).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('mentionsQueryFromUserOption', () => {
|
||||||
|
it('returns null query when option omitted', () => {
|
||||||
|
expect(mentionsQueryFromUserOption(undefined)).toEqual({ query: null, error: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns normalized @query for valid handle', () => {
|
||||||
|
expect(mentionsQueryFromUserOption('@steipete')).toEqual({ query: '@steipete', error: null });
|
||||||
|
expect(mentionsQueryFromUserOption(' steipete ')).toEqual({ query: '@steipete', error: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error for invalid handle', () => {
|
||||||
|
const result = mentionsQueryFromUserOption('@stei-pete');
|
||||||
|
expect(result.query).toBeNull();
|
||||||
|
expect(result.error).toMatch(/Invalid --user handle/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user