@spectratools/xapi-cli 0.3.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/cli.js +191 -5
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -189,6 +189,15 @@ function createXApiClient(bearerToken) {
189
189
  function likePost(userId, tweetId) {
190
190
  return post(`/users/${userId}/likes`, { tweet_id: tweetId });
191
191
  }
192
+ function unlikePost(userId, tweetId) {
193
+ return del(`/users/${userId}/likes/${tweetId}`);
194
+ }
195
+ function bookmarkPost(userId, tweetId) {
196
+ return post(`/users/${userId}/bookmarks`, { tweet_id: tweetId });
197
+ }
198
+ function unbookmarkPost(userId, tweetId) {
199
+ return del(`/users/${userId}/bookmarks/${tweetId}`);
200
+ }
192
201
  function retweetPost(userId, tweetId) {
193
202
  return post(`/users/${userId}/retweets`, { tweet_id: tweetId });
194
203
  }
@@ -225,6 +234,22 @@ function createXApiClient(bearerToken) {
225
234
  function unfollowUser(sourceUserId, targetUserId) {
226
235
  return del(`/users/${sourceUserId}/following/${targetUserId}`);
227
236
  }
237
+ function blockUser(sourceUserId, targetUserId) {
238
+ return post(`/users/${sourceUserId}/blocking`, {
239
+ target_user_id: targetUserId
240
+ });
241
+ }
242
+ function unblockUser(sourceUserId, targetUserId) {
243
+ return del(`/users/${sourceUserId}/blocking/${targetUserId}`);
244
+ }
245
+ function muteUser(sourceUserId, targetUserId) {
246
+ return post(`/users/${sourceUserId}/muting`, {
247
+ target_user_id: targetUserId
248
+ });
249
+ }
250
+ function unmuteUser(sourceUserId, targetUserId) {
251
+ return del(`/users/${sourceUserId}/muting/${targetUserId}`);
252
+ }
228
253
  function getUserPosts(id, maxResults, nextToken) {
229
254
  return get(`/users/${id}/tweets`, {
230
255
  max_results: maxResults,
@@ -280,6 +305,24 @@ function createXApiClient(bearerToken) {
280
305
  ...nextToken ? { pagination_token: nextToken } : {}
281
306
  });
282
307
  }
308
+ function createList(name, description, isPrivate) {
309
+ return post("/lists", {
310
+ name,
311
+ ...description ? { description } : {},
312
+ ...isPrivate !== void 0 ? { private: isPrivate } : {}
313
+ });
314
+ }
315
+ function deleteList(id) {
316
+ return del(`/lists/${id}`);
317
+ }
318
+ function addListMember(id, userId) {
319
+ return post(`/lists/${id}/members`, {
320
+ user_id: userId
321
+ });
322
+ }
323
+ function removeListMember(id, userId) {
324
+ return del(`/lists/${id}/members/${userId}`);
325
+ }
283
326
  function getTrendingPlaces() {
284
327
  return getV1_1(
285
328
  "/trends/available.json"
@@ -318,6 +361,9 @@ function createXApiClient(bearerToken) {
318
361
  getPostLikes,
319
362
  getPostRetweets,
320
363
  likePost,
364
+ unlikePost,
365
+ bookmarkPost,
366
+ unbookmarkPost,
321
367
  retweetPost,
322
368
  getUserByUsername,
323
369
  getUserById,
@@ -325,6 +371,10 @@ function createXApiClient(bearerToken) {
325
371
  getUserFollowing,
326
372
  followUser,
327
373
  unfollowUser,
374
+ blockUser,
375
+ unblockUser,
376
+ muteUser,
377
+ unmuteUser,
328
378
  getUserPosts,
329
379
  getUserMentions,
330
380
  searchUsers,
@@ -333,6 +383,10 @@ function createXApiClient(bearerToken) {
333
383
  getList,
334
384
  getListMembers,
335
385
  getListPosts,
386
+ createList,
387
+ deleteList,
388
+ addListMember,
389
+ removeListMember,
336
390
  getTrendingPlaces,
337
391
  getTrendsByLocation,
338
392
  getDmConversations,
@@ -791,6 +845,54 @@ posts.command("delete", {
791
845
  }
792
846
  }
793
847
  });
848
+ posts.command("like", {
849
+ description: "Like a post by ID.",
850
+ args: z4.object({
851
+ id: z4.string().describe("Post ID to like")
852
+ }),
853
+ env: xApiWriteEnv,
854
+ output: z4.object({
855
+ liked: z4.boolean(),
856
+ id: z4.string()
857
+ }),
858
+ examples: [{ args: { id: "1234567890" }, description: "Like a post" }],
859
+ async run(c) {
860
+ try {
861
+ const client = createXApiClient(writeAuthToken(c.env));
862
+ const me = await client.getMe();
863
+ const res = await client.likePost(me.data.id, c.args.id);
864
+ return c.ok({ liked: res.data.liked, id: c.args.id });
865
+ } catch (error) {
866
+ const authError = toWriteAuthError("posts like", error);
867
+ if (authError) return c.error(authError);
868
+ throw error;
869
+ }
870
+ }
871
+ });
872
+ posts.command("retweet", {
873
+ description: "Retweet a post by ID.",
874
+ args: z4.object({
875
+ id: z4.string().describe("Post ID to retweet")
876
+ }),
877
+ env: xApiWriteEnv,
878
+ output: z4.object({
879
+ retweeted: z4.boolean(),
880
+ id: z4.string()
881
+ }),
882
+ examples: [{ args: { id: "1234567890" }, description: "Retweet a post" }],
883
+ async run(c) {
884
+ try {
885
+ const client = createXApiClient(writeAuthToken(c.env));
886
+ const me = await client.getMe();
887
+ const res = await client.retweetPost(me.data.id, c.args.id);
888
+ return c.ok({ retweeted: res.data.retweeted, id: c.args.id });
889
+ } catch (error) {
890
+ const authError = toWriteAuthError("posts retweet", error);
891
+ if (authError) return c.error(authError);
892
+ throw error;
893
+ }
894
+ }
895
+ });
794
896
  posts.command("likes", {
795
897
  description: "List users who liked a post.",
796
898
  args: z4.object({
@@ -887,6 +989,7 @@ timeline.command("home", {
887
989
  description: "View your home timeline.",
888
990
  options: z5.object({
889
991
  maxResults: z5.number().default(25).describe("Maximum posts to return (5\u2013100)"),
992
+ sinceId: z5.string().optional().describe("Only return posts newer than this post ID"),
890
993
  verbose: z5.boolean().optional().describe("Show full text without truncation")
891
994
  }),
892
995
  alias: { maxResults: "n" },
@@ -906,14 +1009,18 @@ timeline.command("home", {
906
1009
  }),
907
1010
  examples: [
908
1011
  { description: "View your home timeline" },
909
- { options: { maxResults: 50 }, description: "View 50 posts" }
1012
+ { options: { maxResults: 50 }, description: "View 50 posts" },
1013
+ {
1014
+ options: { sinceId: "1900123456789012345" },
1015
+ description: "Resume from last-seen post ID"
1016
+ }
910
1017
  ],
911
1018
  async run(c) {
912
1019
  const client = createXApiClient(readAuthToken(c.env));
913
1020
  const meRes = await client.getMe();
914
1021
  const userId = meRes.data.id;
915
1022
  const allPosts = await collectPaged(
916
- (limit, cursor) => client.getHomeTimeline(userId, limit, cursor),
1023
+ (limit, cursor) => client.getHomeTimeline(userId, limit, cursor, c.options.sinceId),
917
1024
  (post) => ({
918
1025
  id: post.id,
919
1026
  text: c.options.verbose ? post.text : truncateText(post.text),
@@ -946,6 +1053,7 @@ timeline.command("mentions", {
946
1053
  description: "View your recent mentions.",
947
1054
  options: z5.object({
948
1055
  maxResults: z5.number().default(25).describe("Maximum mentions to return"),
1056
+ sinceId: z5.string().optional().describe("Only return mentions newer than this post ID"),
949
1057
  verbose: z5.boolean().optional().describe("Show full text without truncation")
950
1058
  }),
951
1059
  alias: { maxResults: "n" },
@@ -961,13 +1069,19 @@ timeline.command("mentions", {
961
1069
  ),
962
1070
  count: z5.number()
963
1071
  }),
964
- examples: [{ description: "View your recent mentions" }],
1072
+ examples: [
1073
+ { description: "View your recent mentions" },
1074
+ {
1075
+ options: { sinceId: "1900123456789012345" },
1076
+ description: "Resume mentions from last-seen post ID"
1077
+ }
1078
+ ],
965
1079
  async run(c) {
966
1080
  const client = createXApiClient(readAuthToken(c.env));
967
1081
  const meRes = await client.getMe();
968
1082
  const userId = meRes.data.id;
969
1083
  const allPosts = await collectPaged(
970
- (limit, cursor) => client.getMentionsTimeline(userId, limit, cursor),
1084
+ (limit, cursor) => client.getMentionsTimeline(userId, limit, cursor, c.options.sinceId),
971
1085
  (post) => ({
972
1086
  id: post.id,
973
1087
  text: c.options.verbose ? post.text : truncateText(post.text),
@@ -1198,6 +1312,70 @@ users.command("get", {
1198
1312
  );
1199
1313
  }
1200
1314
  });
1315
+ users.command("follow", {
1316
+ description: "Follow a user by username or ID.",
1317
+ args: z7.object({
1318
+ username: z7.string().describe("Username (with or without @) or user ID")
1319
+ }),
1320
+ env: xApiWriteEnv,
1321
+ output: z7.object({
1322
+ id: z7.string(),
1323
+ username: z7.string(),
1324
+ following: z7.boolean(),
1325
+ pending_follow: z7.boolean().optional()
1326
+ }),
1327
+ examples: [{ args: { username: "jack" }, description: "Follow @jack" }],
1328
+ async run(c) {
1329
+ try {
1330
+ const client = createXApiClient(writeAuthToken(c.env));
1331
+ const me = await client.getMe();
1332
+ const targetRes = await resolveUser(client, c.args.username);
1333
+ const target = targetRes.data;
1334
+ const res = await client.followUser(me.data.id, target.id);
1335
+ return c.ok({
1336
+ id: target.id,
1337
+ username: target.username,
1338
+ following: res.data.following,
1339
+ pending_follow: res.data.pending_follow
1340
+ });
1341
+ } catch (error) {
1342
+ const authError = toWriteAuthError("users follow", error);
1343
+ if (authError) return c.error(authError);
1344
+ throw error;
1345
+ }
1346
+ }
1347
+ });
1348
+ users.command("unfollow", {
1349
+ description: "Unfollow a user by username or ID.",
1350
+ args: z7.object({
1351
+ username: z7.string().describe("Username (with or without @) or user ID")
1352
+ }),
1353
+ env: xApiWriteEnv,
1354
+ output: z7.object({
1355
+ id: z7.string(),
1356
+ username: z7.string(),
1357
+ following: z7.boolean()
1358
+ }),
1359
+ examples: [{ args: { username: "jack" }, description: "Unfollow @jack" }],
1360
+ async run(c) {
1361
+ try {
1362
+ const client = createXApiClient(writeAuthToken(c.env));
1363
+ const me = await client.getMe();
1364
+ const targetRes = await resolveUser(client, c.args.username);
1365
+ const target = targetRes.data;
1366
+ const res = await client.unfollowUser(me.data.id, target.id);
1367
+ return c.ok({
1368
+ id: target.id,
1369
+ username: target.username,
1370
+ following: res.data.following
1371
+ });
1372
+ } catch (error) {
1373
+ const authError = toWriteAuthError("users unfollow", error);
1374
+ if (authError) return c.error(authError);
1375
+ throw error;
1376
+ }
1377
+ }
1378
+ });
1201
1379
  users.command("followers", {
1202
1380
  description: "List followers of a user. Supports optional client-side baseline diffing for new follower detection.",
1203
1381
  args: z7.object({
@@ -1439,7 +1617,15 @@ var cli = Cli7.create("xapi", {
1439
1617
  version: pkg.version,
1440
1618
  description: "X (Twitter) API CLI for spectra-the-bot."
1441
1619
  });
1442
- var WRITE_OPERATIONS = /* @__PURE__ */ new Set(["posts create", "posts delete", "dm send"]);
1620
+ var WRITE_OPERATIONS = /* @__PURE__ */ new Set([
1621
+ "posts create",
1622
+ "posts delete",
1623
+ "posts like",
1624
+ "posts retweet",
1625
+ "users follow",
1626
+ "users unfollow",
1627
+ "dm send"
1628
+ ]);
1443
1629
  cli.use(async ({ command, error }, next) => {
1444
1630
  try {
1445
1631
  return await next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectratools/xapi-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "X (Twitter) API CLI for spectra-the-bot",
5
5
  "type": "module",
6
6
  "license": "MIT",