fxtwitter 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,33 +13,61 @@ Typed wrapper for the [FxTwitter](https://github.com/FxEmbed/FxEmbed) API.
13
13
  npm install fxtwitter
14
14
  ```
15
15
 
16
+ ## CLI
17
+
18
+ ```sh
19
+ npx fxtwitter status 20
20
+ npx fxtwitter status "https://x.com/jack/status/20" --pretty
21
+ npx fxtwitter profile @jack --api v1
22
+ npx fxtwitter search "from:jack puppies" | jq '.results'
23
+ ```
24
+
25
+ The CLI uses API v2 by default. Use `--api v1` with `status` or `profile`. Search is available in v2. Profile handles can include a leading `@`.
26
+
27
+ Successful commands write compact JSON to stdout. Use `--pretty` to indent it. Errors go to stderr and exit with a nonzero status.
28
+
29
+ | Option | Description |
30
+ | ------ | ----------- |
31
+ | `--base-url <url>` | Override the API base URL |
32
+ | `--timeout <ms>` | Set the request timeout |
33
+ | `--retry <count>` | Set the retry count (use `0` to disable retries) |
34
+ | `--retry-delay <ms>` | Set the delay between retries |
35
+ | `-h, --help` | Show commands and options. Use `fxtwitter status --help` for help with a specific command |
36
+
16
37
  ## Usage
17
38
 
18
39
  ```ts
19
- import { FxTwitterV1 } from "fxtwitter";
40
+ import { FxTwitterV2 } from "fxtwitter/v2";
20
41
 
21
- const fx = new FxTwitterV1();
42
+ const fx = new FxTwitterV2();
22
43
 
23
- const { tweet } = await fx.getStatus("20");
24
- const { user } = await fx.getUser("jack");
44
+ const { status } = await fx.getStatus("20");
45
+ const { user } = await fx.getProfile("X");
46
+ const { results } = await fx.search("puppies");
25
47
  ```
26
48
 
27
- Types can be imported alongside the client:
49
+ Each version ships under its own subpath, where its types are exported
50
+ alongside the client:
28
51
 
29
52
  ```ts
30
- import { FxTwitterV1, type Tweet, type User } from "fxtwitter";
53
+ import { FxTwitterV2, type TwitterStatus } from "fxtwitter/v2";
54
+ import { FxTwitterV1, type Tweet } from "fxtwitter/v1";
31
55
  ```
32
56
 
33
- The client is also available from the `fxtwitter/v1` subpath, which will keep
34
- working unchanged as further API versions are added to the root:
57
+ If you need both at once, `FxTwitter` bundles them. `v1` is only constructed —
58
+ and so only warns — on first access:
35
59
 
36
60
  ```ts
37
- import { FxTwitterV1 } from "fxtwitter/v1";
61
+ import { FxTwitter } from "fxtwitter";
62
+
63
+ const fx = new FxTwitter({ v1: { silenceDeprecationWarning: true } });
64
+ await fx.v2.getStatus("20");
65
+ await fx.v1.getStatus("20");
38
66
  ```
39
67
 
40
- ## API
68
+ ## Options
41
69
 
42
- ### `new FxTwitterV1(options?)`
70
+ Every client takes the same options:
43
71
 
44
72
  | Option | Type | Default | Description |
45
73
  | --- | --- | --- | --- |
@@ -50,13 +78,15 @@ import { FxTwitterV1 } from "fxtwitter/v1";
50
78
  | `retryDelay` | `number` | `0` | Delay between retries, in ms |
51
79
  | `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation |
52
80
 
81
+ Every method takes an optional `signal` for per-call cancellation.
82
+
53
83
  > The API requires a `User-Agent` identifying the caller and answers `401`
54
84
  > without one. When you don't set one, a header describing the current runtime
55
85
  > is sent — `Node.js/22.16.0`, `Bun/1.3.0`, `Cloudflare-Workers` and so on.
56
86
  > Set your own to identify your app instead:
57
87
  >
58
88
  > ```ts
59
- > new FxTwitterV1({
89
+ > new FxTwitterV2({
60
90
  > headers: { "User-Agent": "MyApp/1.0 (+https://example.com)" },
61
91
  > });
62
92
  > ```
@@ -64,14 +94,69 @@ import { FxTwitterV1 } from "fxtwitter/v1";
64
94
  > Nothing is sent in browsers, where `User-Agent` is a forbidden header and the
65
95
  > browser supplies its own.
66
96
 
67
- ### `getStatus(id, options?)`
97
+ ## v2
98
+
99
+ ### `new FxTwitterV2(options?)`
100
+
101
+ | Method | Returns |
102
+ | --- | --- |
103
+ | `getStatus(id, options?)` | A single post |
104
+ | `getThread(id, options?)` | A post with its unrolled thread |
105
+ | `getConversation(id, options?)` | A post, its thread, and ranked replies |
106
+ | `getStatusReposts(id, options?)` | Users who reposted a post |
107
+ | `getStatusQuotes(id, options?)` | Posts quoting a post |
108
+ | `getProfile(handle, options?)` | A user profile |
109
+ | `getProfileStatuses(handle, options?)` | A user's posts |
110
+ | `getProfileArticles(handle, options?)` | A user's long-form articles |
111
+ | `getProfileMedia(handle, options?)` | A user's posts containing media |
112
+ | `getProfileAbout(handle, options?)` | Account metadata |
113
+ | `getProfileFollowers(handle, options?)` | A user's followers |
114
+ | `getProfileFollowing(handle, options?)` | Accounts a user follows |
115
+ | `search(query, options?)` | Post search results |
116
+ | `typeahead(query, options?)` | Autocomplete suggestions |
117
+ | `trends(options?)` | Trending topics |
118
+
119
+ Common options: `count` and `cursor` paginate list endpoints, `lang` requests an
120
+ inline translation, and `aboutAccount` adds `about_account` to an author.
121
+
122
+ ```ts
123
+ // Paginate
124
+ let cursor: string | undefined;
125
+ do {
126
+ const page = await fx.search("puppies", { feed: "top", count: 50, cursor });
127
+ cursor = page.cursor.bottom ?? undefined;
128
+ } while (cursor);
129
+
130
+ // Look a profile up by numeric ID
131
+ import { byUserId } from "fxtwitter/v2";
132
+ await fx.getProfile(byUserId("783214"));
133
+ ```
134
+
135
+ The list endpoints report "no results" as a `404` carrying an otherwise normal
136
+ body. An empty page is a result rather than an error, so it resolves with
137
+ `results: []`. The API uses that same `404` for an unknown handle, which it does
138
+ not distinguish from an empty timeline.
139
+
140
+ `getProfileStatuses` resolves to `null` when `since` is set without a `cursor`
141
+ and nothing is newer — the API's documented `204`. Every other method either
142
+ resolves with a body or throws.
143
+
144
+ ## v1
145
+
146
+ > **Deprecated.** v1 is kept only for backwards compatibility and does not
147
+ > receive new features — use [v2](#v2) instead. Constructing `FxTwitterV1`
148
+ > emits a one-time `DeprecationWarning` (so `--no-deprecation` and
149
+ > `--throw-deprecation` apply); pass `silenceDeprecationWarning: true` to
150
+ > suppress it. `FxTwitter`'s `v1` property only constructs the client, and so
151
+ > only warns, on first access.
152
+
153
+ ### `new FxTwitterV1(options?)`
154
+
155
+ #### `getStatus(id, options?)`
68
156
 
69
157
  Fetches a single status by its snowflake ID.
70
158
 
71
159
  ```ts
72
- const { tweet } = await fx.getStatus("20");
73
-
74
- // Translate the status
75
160
  const { tweet } = await fx.getStatus("20", { translateTo: "es" });
76
161
  tweet?.translation?.text;
77
162
  ```
@@ -80,12 +165,11 @@ tweet?.translation?.text;
80
165
  | --- | --- | --- |
81
166
  | `translateTo` | `string` | Target language — an ISO 639-1 code (`es`) or locale (`zh-cn`). Adds `translation` to the status |
82
167
  | `screenName` | `string` | Author handle, for a readable URL. The API resolves the status from `id` alone and never checks it |
83
- | `signal` | `AbortSignal` | Per-call cancellation |
84
168
 
85
169
  Resolves to `{ code, message, tweet }`. `tweet` is `null` when the status could
86
170
  not be retrieved.
87
171
 
88
- ### `getUser(handle, options?)`
172
+ #### `getUser(handle, options?)`
89
173
 
90
174
  Fetches a user profile by handle, without a leading `@`.
91
175
 
@@ -93,15 +177,19 @@ Fetches a user profile by handle, without a leading `@`.
93
177
  const { user } = await fx.getUser("jack");
94
178
  ```
95
179
 
96
- | Option | Type | Description |
97
- | --- | --- | --- |
98
- | `signal` | `AbortSignal` | Per-call cancellation |
99
-
100
180
  Resolves to `{ code, message, user, reason? }`. `user` is absent when the
101
181
  profile could not be retrieved, and `reason` is `"suspended"` for a suspended
102
182
  account.
103
183
 
104
- ### Errors
184
+ On success, `code` mirrors the HTTP status and `message` is one of `OK`,
185
+ `PRIVATE_TWEET`, `NOT_FOUND`, `UPSTREAM_UNAVAILABLE` or `API_FAIL`.
186
+
187
+ Malformed input is rejected before a request is sent, because v1 answers those
188
+ cases with HTML or a redirect rather than JSON: a status ID must be 2-20 digits,
189
+ and a handle passed to `getUser` must match `\w{1,15}`. A well-formed handle
190
+ that does not exist is a normal `404`.
191
+
192
+ ## Errors
105
193
 
106
194
  HTTP 4xx/5xx, network failures, timeouts, invalid input, and non-JSON responses
107
195
  are thrown as `FxTwitterError`:
@@ -122,18 +210,10 @@ try {
122
210
  }
123
211
  ```
124
212
 
125
- On success, `code` mirrors the HTTP status and `message` is one of `OK`,
126
- `PRIVATE_TWEET`, `NOT_FOUND`, `UPSTREAM_UNAVAILABLE` or `API_FAIL`.
127
-
128
213
  For a network failure or a timeout there is no response, so `status`, `code`
129
214
  and `body` are all `undefined` and `message` is a generic description. The
130
215
  original error is still available as `cause`.
131
216
 
132
- Malformed input is rejected before a request is sent, because the API answers
133
- those cases with HTML or a redirect rather than JSON: a status ID must be 2-20
134
- digits, and a handle passed to `getUser` must match `\w{1,15}`. A well-formed
135
- handle that does not exist is a normal `404`.
136
-
137
217
  ## Requirements
138
218
 
139
219
  - Node.js >= 22
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Pooya Parsa <pooya@pi0.io>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # Licenses of Bundled Dependencies
2
+
3
+ The published artifact additionally contains code with the following licenses:
4
+ MIT
5
+
6
+ # Bundled Dependencies
7
+
8
+ ## scule
9
+
10
+ License: MIT
11
+ Repository: https://github.com/unjs/scule
12
+
13
+ > MIT License
14
+ >
15
+ > Copyright (c) Pooya Parsa <pooya@pi0.io>
16
+ >
17
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
18
+ > of this software and associated documentation files (the "Software"), to deal
19
+ > in the Software without restriction, including without limitation the rights
20
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
+ > copies of the Software, and to permit persons to whom the Software is
22
+ > furnished to do so, subject to the following conditions:
23
+ >
24
+ > The above copyright notice and this permission notice shall be included in all
25
+ > copies or substantial portions of the Software.
26
+ >
27
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
+ > SOFTWARE.