From e537e102e3310c5a34b61356e90461b42ac42dda Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 3 Jan 2026 16:05:42 +0100 Subject: [PATCH] fix: normalize list privacy parsing --- CHANGELOG.md | 2 +- src/cli/program.ts | 2 +- src/lib/twitter-client-lists.ts | 2 +- tests/twitter-client.lists.test.ts | 49 ++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f51f684..4165b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added - Bookmark exports now support pagination (`--all`, `--max-pages`) with retries (#15) — thanks @Nano1337. - +- `lists` + `list-timeline` commands for Twitter Lists (#21) — thanks @harperreed ## 0.5.1 — 2026-01-01 ### Changed diff --git a/src/cli/program.ts b/src/cli/program.ts index 46ba7b3..86f4396 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -84,7 +84,7 @@ export function createProgram(ctx: CliContext): Command { ].join('\n\n')}\n\n${ctx.colors.section('Shortcuts')}\n${[ formatExample('bird [--json]', 'Shorthand for `bird read `'), ].join('\n\n')}\n\n${ctx.colors.section('JSON Output')}\n${ctx.colors.muted( - ` Add ${ctx.colors.option('--json')} to: read, replies, thread, search, mentions, bookmarks, likes, following, followers, query-ids`, + ` Add ${ctx.colors.option('--json')} to: read, replies, thread, search, mentions, bookmarks, likes, following, followers, lists, list-timeline, query-ids`, )}\n${ctx.colors.muted( ` Add ${ctx.colors.option('--json-full')} to include raw API response in ${ctx.colors.argument('_raw')} field (tweet commands only)`, )}\n${ctx.colors.muted(` (Run ${ctx.colors.command('bird --help')} to see per-command flags.)`)}`, diff --git a/src/lib/twitter-client-lists.ts b/src/lib/twitter-client-lists.ts index 9651201..4929970 100644 --- a/src/lib/twitter-client-lists.ts +++ b/src/lib/twitter-client-lists.ts @@ -45,7 +45,7 @@ function parseList(listResult: GraphqlListResult): TwitterList | null { description: listResult.description, memberCount: listResult.member_count, subscriberCount: listResult.subscriber_count, - isPrivate: listResult.mode === 'Private', + isPrivate: listResult.mode?.toLowerCase() === 'private', createdAt: listResult.created_at, owner: owner ? { diff --git a/tests/twitter-client.lists.test.ts b/tests/twitter-client.lists.test.ts index 56dacad..e8fa3a8 100644 --- a/tests/twitter-client.lists.test.ts +++ b/tests/twitter-client.lists.test.ts @@ -136,6 +136,55 @@ describe('TwitterClient lists', () => { expect(result.lists?.[0].isPrivate).toBe(true); }); + it('handles lowercase private mode', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + data: { + user: { + result: { + timeline: { + timeline: { + instructions: [ + { + entries: [ + { + content: { + itemContent: { + list: { + id_str: '8888', + name: 'Lowercase Private', + mode: 'private', + }, + }, + }, + }, + ], + }, + ], + }, + }, + }, + }, + }, + }), + }); + + const client = new TwitterClient({ cookies: validCookies }); + const clientPrivate = client as unknown as TwitterClientPrivate; + clientPrivate.getCurrentUser = async () => ({ + success: true, + user: { id: '12345', username: 'testuser', name: 'Test User' }, + }); + clientPrivate.getListOwnershipsQueryIds = async () => ['test']; + + const result = await client.getOwnedLists(100); + + expect(result.success).toBe(true); + expect(result.lists?.[0].isPrivate).toBe(true); + }); + it('returns empty array when no lists exist', async () => { mockFetch.mockResolvedValueOnce({ ok: true,