@opencoredev/social-sdk 0.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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/dist/cli.d.ts +14 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +223 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cloud/common.d.ts +26 -0
  8. package/dist/cloud/common.d.ts.map +1 -0
  9. package/dist/cloud/common.js +307 -0
  10. package/dist/cloud/common.js.map +1 -0
  11. package/dist/cloud/lifecycle.d.ts +11 -0
  12. package/dist/cloud/lifecycle.d.ts.map +1 -0
  13. package/dist/cloud/lifecycle.js +108 -0
  14. package/dist/cloud/lifecycle.js.map +1 -0
  15. package/dist/cloud/media.d.ts +24 -0
  16. package/dist/cloud/media.d.ts.map +1 -0
  17. package/dist/cloud/media.js +97 -0
  18. package/dist/cloud/media.js.map +1 -0
  19. package/dist/cloud/outcomes.d.ts +10 -0
  20. package/dist/cloud/outcomes.d.ts.map +1 -0
  21. package/dist/cloud/outcomes.js +188 -0
  22. package/dist/cloud/outcomes.js.map +1 -0
  23. package/dist/cloud/post-for-me.d.ts +70 -0
  24. package/dist/cloud/post-for-me.d.ts.map +1 -0
  25. package/dist/cloud/post-for-me.js +326 -0
  26. package/dist/cloud/post-for-me.js.map +1 -0
  27. package/dist/cloud/zernio.d.ts +112 -0
  28. package/dist/cloud/zernio.d.ts.map +1 -0
  29. package/dist/cloud/zernio.js +542 -0
  30. package/dist/cloud/zernio.js.map +1 -0
  31. package/dist/core/adapter.d.ts +118 -0
  32. package/dist/core/adapter.d.ts.map +1 -0
  33. package/dist/core/adapter.js +4 -0
  34. package/dist/core/adapter.js.map +1 -0
  35. package/dist/core/client.d.ts +108 -0
  36. package/dist/core/client.d.ts.map +1 -0
  37. package/dist/core/client.js +893 -0
  38. package/dist/core/client.js.map +1 -0
  39. package/dist/core/concurrency.d.ts +10 -0
  40. package/dist/core/concurrency.d.ts.map +1 -0
  41. package/dist/core/concurrency.js +78 -0
  42. package/dist/core/concurrency.js.map +1 -0
  43. package/dist/core/errors.d.ts +45 -0
  44. package/dist/core/errors.d.ts.map +1 -0
  45. package/dist/core/errors.js +44 -0
  46. package/dist/core/errors.js.map +1 -0
  47. package/dist/core/idempotency.d.ts +34 -0
  48. package/dist/core/idempotency.d.ts.map +1 -0
  49. package/dist/core/idempotency.js +59 -0
  50. package/dist/core/idempotency.js.map +1 -0
  51. package/dist/core/index.d.ts +7 -0
  52. package/dist/core/index.d.ts.map +1 -0
  53. package/dist/core/index.js +7 -0
  54. package/dist/core/index.js.map +1 -0
  55. package/dist/core/pagination.d.ts +12 -0
  56. package/dist/core/pagination.d.ts.map +1 -0
  57. package/dist/core/pagination.js +81 -0
  58. package/dist/core/pagination.js.map +1 -0
  59. package/dist/core/types.d.ts +329 -0
  60. package/dist/core/types.d.ts.map +1 -0
  61. package/dist/core/types.js +7 -0
  62. package/dist/core/types.js.map +1 -0
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/platforms/bluesky.d.ts +132 -0
  68. package/dist/platforms/bluesky.d.ts.map +1 -0
  69. package/dist/platforms/bluesky.js +1049 -0
  70. package/dist/platforms/bluesky.js.map +1 -0
  71. package/dist/platforms/instagram.d.ts +63 -0
  72. package/dist/platforms/instagram.d.ts.map +1 -0
  73. package/dist/platforms/instagram.js +676 -0
  74. package/dist/platforms/instagram.js.map +1 -0
  75. package/dist/platforms/linkedin.d.ts +54 -0
  76. package/dist/platforms/linkedin.d.ts.map +1 -0
  77. package/dist/platforms/linkedin.js +580 -0
  78. package/dist/platforms/linkedin.js.map +1 -0
  79. package/dist/platforms/threads.d.ts +102 -0
  80. package/dist/platforms/threads.d.ts.map +1 -0
  81. package/dist/platforms/threads.js +571 -0
  82. package/dist/platforms/threads.js.map +1 -0
  83. package/dist/platforms/tiktok.d.ts +32 -0
  84. package/dist/platforms/tiktok.d.ts.map +1 -0
  85. package/dist/platforms/tiktok.js +511 -0
  86. package/dist/platforms/tiktok.js.map +1 -0
  87. package/dist/platforms/x-engagement.d.ts +17 -0
  88. package/dist/platforms/x-engagement.d.ts.map +1 -0
  89. package/dist/platforms/x-engagement.js +47 -0
  90. package/dist/platforms/x-engagement.js.map +1 -0
  91. package/dist/platforms/x.d.ts +82 -0
  92. package/dist/platforms/x.d.ts.map +1 -0
  93. package/dist/platforms/x.js +511 -0
  94. package/dist/platforms/x.js.map +1 -0
  95. package/dist/platforms/youtube-upload.d.ts +33 -0
  96. package/dist/platforms/youtube-upload.d.ts.map +1 -0
  97. package/dist/platforms/youtube-upload.js +281 -0
  98. package/dist/platforms/youtube-upload.js.map +1 -0
  99. package/dist/platforms/youtube.d.ts +55 -0
  100. package/dist/platforms/youtube.d.ts.map +1 -0
  101. package/dist/platforms/youtube.js +561 -0
  102. package/dist/platforms/youtube.js.map +1 -0
  103. package/dist/server/connections.d.ts +124 -0
  104. package/dist/server/connections.d.ts.map +1 -0
  105. package/dist/server/connections.js +334 -0
  106. package/dist/server/connections.js.map +1 -0
  107. package/dist/server/credentials.d.ts +52 -0
  108. package/dist/server/credentials.d.ts.map +1 -0
  109. package/dist/server/credentials.js +115 -0
  110. package/dist/server/credentials.js.map +1 -0
  111. package/dist/server/index.d.ts +5 -0
  112. package/dist/server/index.d.ts.map +1 -0
  113. package/dist/server/index.js +5 -0
  114. package/dist/server/index.js.map +1 -0
  115. package/dist/server/oauth.d.ts +48 -0
  116. package/dist/server/oauth.d.ts.map +1 -0
  117. package/dist/server/oauth.js +483 -0
  118. package/dist/server/oauth.js.map +1 -0
  119. package/dist/server/webhooks.d.ts +58 -0
  120. package/dist/server/webhooks.d.ts.map +1 -0
  121. package/dist/server/webhooks.js +198 -0
  122. package/dist/server/webhooks.js.map +1 -0
  123. package/dist/testing/index.d.ts +47 -0
  124. package/dist/testing/index.d.ts.map +1 -0
  125. package/dist/testing/index.js +413 -0
  126. package/dist/testing/index.js.map +1 -0
  127. package/dist/testing.d.ts +2 -0
  128. package/dist/testing.d.ts.map +1 -0
  129. package/dist/testing.js +2 -0
  130. package/dist/testing.js.map +1 -0
  131. package/dist/transport/binary.d.ts +3 -0
  132. package/dist/transport/binary.d.ts.map +1 -0
  133. package/dist/transport/binary.js +30 -0
  134. package/dist/transport/binary.js.map +1 -0
  135. package/dist/transport/budget.d.ts +4 -0
  136. package/dist/transport/budget.d.ts.map +1 -0
  137. package/dist/transport/budget.js +27 -0
  138. package/dist/transport/budget.js.map +1 -0
  139. package/dist/transport/http.d.ts +44 -0
  140. package/dist/transport/http.d.ts.map +1 -0
  141. package/dist/transport/http.js +230 -0
  142. package/dist/transport/http.js.map +1 -0
  143. package/dist/transport/json.d.ts +3 -0
  144. package/dist/transport/json.d.ts.map +1 -0
  145. package/dist/transport/json.js +11 -0
  146. package/dist/transport/json.js.map +1 -0
  147. package/dist/transport/upload.d.ts +25 -0
  148. package/dist/transport/upload.d.ts.map +1 -0
  149. package/dist/transport/upload.js +133 -0
  150. package/dist/transport/upload.js.map +1 -0
  151. package/dist/transport/validation.d.ts +6 -0
  152. package/dist/transport/validation.d.ts.map +1 -0
  153. package/dist/transport/validation.js +24 -0
  154. package/dist/transport/validation.js.map +1 -0
  155. package/package.json +96 -0
@@ -0,0 +1,47 @@
1
+ import { SocialError } from "../core/errors.js";
2
+ import { managedHttp } from "../cloud/common.js";
3
+ async function mutate(action, tweetId, account, options, context) {
4
+ if (account.kind !== "connected-account" ||
5
+ account.version !== 1 ||
6
+ account.backend !== context.backendInstance ||
7
+ account.platform !== "x" ||
8
+ account.accountId !== options.userId)
9
+ throw new SocialError({
10
+ code: "unauthorized",
11
+ operation: `x.${action}`,
12
+ message: "Account reference does not belong to this X authorization and backend.",
13
+ });
14
+ if (!/^\d+$/.test(tweetId))
15
+ throw new SocialError({
16
+ code: "invalid_input",
17
+ operation: `x.${action}`,
18
+ message: "Provide a native numeric X post ID.",
19
+ });
20
+ const request = managedHttp("https://api.x.com", {
21
+ apiKey: options.accessToken,
22
+ ...(options.fetch ? { fetch: options.fetch } : {}),
23
+ });
24
+ const result = await request(`/2/users/${encodeURIComponent(options.userId)}/likes${action === "unlike" ? `/${tweetId}` : ""}`, context, action === "like" ? { tweet_id: tweetId } : undefined, {}, action === "like" ? "POST" : "DELETE");
25
+ const data = result && typeof result === "object" && "data" in result ? result.data : undefined;
26
+ if (!data ||
27
+ typeof data !== "object" ||
28
+ !("liked" in data) ||
29
+ typeof data.liked !== "boolean" ||
30
+ data.liked !== (action === "like"))
31
+ throw new SocialError({
32
+ code: "ambiguous_outcome",
33
+ operation: `x.${action}`,
34
+ message: "X did not confirm the requested reaction state. Reconcile before retrying.",
35
+ retryDisposition: { kind: "reconcile-first" },
36
+ });
37
+ return { liked: data.liked, tweetId, userId: options.userId };
38
+ }
39
+ /** Requires like.write, tweet.read, users.read and the app's current API access. Never retries writes. */
40
+ export function xLike(tweetId, account, options, context) {
41
+ return mutate("like", tweetId, account, options, context);
42
+ }
43
+ /** Deletes only the authenticated user's like; it does not delete the target post. */
44
+ export function xUnlike(tweetId, account, options, context) {
45
+ return mutate("unlike", tweetId, account, options, context);
46
+ }
47
+ //# sourceMappingURL=x-engagement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x-engagement.js","sourceRoot":"","sources":["../../src/platforms/x-engagement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAajD,KAAK,UAAU,MAAM,CACnB,MAAyB,EACzB,OAAe,EACf,OAA4B,EAC5B,OAA2B,EAC3B,OAAgC;IAEhC,IACE,OAAO,CAAC,IAAI,KAAK,mBAAmB;QACpC,OAAO,CAAC,OAAO,KAAK,CAAC;QACrB,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,eAAe;QAC3C,OAAO,CAAC,QAAQ,KAAK,GAAG;QACxB,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM;QAEpC,MAAM,IAAI,WAAW,CAAC;YACpB,IAAI,EAAE,cAAc;YACpB,SAAS,EAAE,KAAK,MAAM,EAAE;YACxB,OAAO,EAAE,wEAAwE;SAClF,CAAC,CAAC;IACL,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACxB,MAAM,IAAI,WAAW,CAAC;YACpB,IAAI,EAAE,eAAe;YACrB,SAAS,EAAE,KAAK,MAAM,EAAE;YACxB,OAAO,EAAE,qCAAqC;SAC/C,CAAC,CAAC;IACL,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,EAAE;QAC/C,MAAM,EAAE,OAAO,CAAC,WAAW;QAC3B,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,YAAY,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EACjG,OAAO,EACP,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,EACrD,EAAE,EACF,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CACtC,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAChG,IACE,CAAC,IAAI;QACL,OAAO,IAAI,KAAK,QAAQ;QACxB,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC;QAClB,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS;QAC/B,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC;QAElC,MAAM,IAAI,WAAW,CAAC;YACpB,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,KAAK,MAAM,EAAE;YACxB,OAAO,EAAE,4EAA4E;YACrF,gBAAgB,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE;SAC9C,CAAC,CAAC;IACL,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAChE,CAAC;AACD,0GAA0G;AAC1G,MAAM,UAAU,KAAK,CACnB,OAAe,EACf,OAA4B,EAC5B,OAA2B,EAC3B,OAAgC;IAEhC,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AACD,sFAAsF;AACtF,MAAM,UAAU,OAAO,CACrB,OAAe,EACf,OAA4B,EAC5B,OAA2B,EAC3B,OAAgC;IAEhC,OAAO,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,82 @@
1
+ import type { AdapterOperationContext, ConnectedAccountRef, JsonObject, PlatformPostRef } from "../core/types.js";
2
+ export interface XAuthorization {
3
+ readonly userId: string;
4
+ readonly accessToken: string;
5
+ readonly handle?: string;
6
+ }
7
+ export { xLike, xUnlike, type XEngagementOptions, type XEngagementResult } from "./x-engagement.js";
8
+ export interface XOptions {
9
+ readonly auth: XAuthorization;
10
+ readonly fetch?: typeof globalThis.fetch;
11
+ readonly clock?: () => Date;
12
+ }
13
+ export interface XNative {
14
+ readonly readPost: (ref: PlatformPostRef, context: AdapterOperationContext) => Promise<JsonObject>;
15
+ readonly repost: (input: {
16
+ readonly account: ConnectedAccountRef;
17
+ readonly postId: string;
18
+ readonly context: AdapterOperationContext;
19
+ }) => Promise<JsonObject>;
20
+ readonly quote: (input: {
21
+ readonly account: ConnectedAccountRef;
22
+ readonly text: string;
23
+ readonly quotedPostId: string;
24
+ readonly context: AdapterOperationContext;
25
+ }) => Promise<JsonObject>;
26
+ readonly deletePost: (input: {
27
+ readonly account: ConnectedAccountRef;
28
+ readonly postId: string;
29
+ readonly context: AdapterOperationContext;
30
+ }) => Promise<void>;
31
+ readonly createPoll: (input: {
32
+ readonly account: ConnectedAccountRef;
33
+ readonly text: string;
34
+ readonly options: readonly string[];
35
+ readonly durationMinutes: number;
36
+ readonly context: AdapterOperationContext;
37
+ }) => Promise<JsonObject>;
38
+ readonly bookmarks: (input: {
39
+ readonly account: ConnectedAccountRef;
40
+ readonly context: AdapterOperationContext;
41
+ }) => Promise<JsonObject>;
42
+ readonly bookmark: (input: {
43
+ readonly account: ConnectedAccountRef;
44
+ readonly postId: string;
45
+ readonly context: AdapterOperationContext;
46
+ }) => Promise<void>;
47
+ readonly removeBookmark: (input: {
48
+ readonly account: ConnectedAccountRef;
49
+ readonly postId: string;
50
+ readonly context: AdapterOperationContext;
51
+ }) => Promise<void>;
52
+ readonly follow: (input: {
53
+ readonly account: ConnectedAccountRef;
54
+ readonly userId: string;
55
+ readonly context: AdapterOperationContext;
56
+ }) => Promise<JsonObject>;
57
+ readonly unfollow: (input: {
58
+ readonly account: ConnectedAccountRef;
59
+ readonly userId: string;
60
+ readonly context: AdapterOperationContext;
61
+ }) => Promise<void>;
62
+ readonly uploadVideo: (input: {
63
+ readonly account: ConnectedAccountRef;
64
+ readonly media: Blob;
65
+ readonly context: AdapterOperationContext;
66
+ }) => Promise<JsonObject>;
67
+ readonly uploadGif: (input: {
68
+ readonly account: ConnectedAccountRef;
69
+ readonly media: Blob;
70
+ readonly context: AdapterOperationContext;
71
+ }) => Promise<JsonObject>;
72
+ readonly listDirectMessages: (input: {
73
+ readonly account: ConnectedAccountRef;
74
+ readonly context: AdapterOperationContext;
75
+ }) => Promise<JsonObject>;
76
+ readonly stream: (input: {
77
+ readonly account: ConnectedAccountRef;
78
+ readonly context: AdapterOperationContext;
79
+ }) => Promise<Response>;
80
+ }
81
+ export declare function x(options: XOptions): import("../core/adapter.js").SocialAdapter<XNative>;
82
+ //# sourceMappingURL=x.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x.d.ts","sourceRoot":"","sources":["../../src/platforms/x.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,511 @@
1
+ import { remainingBudget } from "../transport/budget.js";
2
+ import twitterText from "twitter-text";
3
+ import { defineAdapter } from "../core/adapter.js";
4
+ import { SocialError } from "../core/errors.js";
5
+ import { managedHttp, publicFields } from "../cloud/common.js";
6
+ import { createHttp, HttpError } from "../transport/http.js";
7
+ import { array, object, optionalNumber, optionalString, string } from "../transport/validation.js";
8
+ export { xLike, xUnlike } from "./x-engagement.js";
9
+ export function x(options) {
10
+ const request = managedHttp("https://api.x.com", {
11
+ apiKey: options.auth.accessToken,
12
+ ...(options.fetch ? { fetch: options.fetch } : {}),
13
+ });
14
+ const http = createHttp(options.fetch ? { fetch: options.fetch } : {});
15
+ const now = () => (options.clock?.() ?? new Date()).toISOString();
16
+ const authorize = (ref, context) => {
17
+ if (ref.backend !== context.backendInstance ||
18
+ ref.platform !== "x" ||
19
+ ref.accountId !== options.auth.userId)
20
+ throw new SocialError({
21
+ code: "unauthorized",
22
+ operation: "x",
23
+ message: "Reference does not match this X user authorization.",
24
+ });
25
+ };
26
+ async function readAccount(context) {
27
+ const user = object(object(await request("/2/users/me", context))["data"]);
28
+ if (user["id"] !== options.auth.userId)
29
+ throw new SocialError({
30
+ code: "unauthorized",
31
+ operation: "accounts.read",
32
+ message: "The authenticated X user differs from the configured account.",
33
+ });
34
+ return {
35
+ ref: {
36
+ kind: "connected-account",
37
+ version: 1,
38
+ backend: context.backendInstance,
39
+ platform: "x",
40
+ accountId: options.auth.userId,
41
+ },
42
+ displayName: string(user["name"]),
43
+ ...(typeof user["username"] === "string" ? { handle: user["username"] } : {}),
44
+ status: "connected",
45
+ };
46
+ }
47
+ async function readPost(ref, context) {
48
+ authorize(ref, context);
49
+ const result = object(object(await request(`/2/tweets/${encodeURIComponent(ref.postId)}`, context, undefined, {
50
+ "tweet.fields": "author_id,public_metrics,created_at,conversation_id,referenced_tweets",
51
+ }))["data"]);
52
+ if (result["id"] !== ref.postId || result["author_id"] !== ref.accountId)
53
+ throw new SocialError({
54
+ code: "unauthorized",
55
+ operation: "posts.read",
56
+ message: "X post identity or author does not match the declared reference.",
57
+ });
58
+ return result;
59
+ }
60
+ async function listPosts(account, input, context) {
61
+ authorize(account, context);
62
+ if (input.limit !== undefined &&
63
+ (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100))
64
+ throw new SocialError({
65
+ code: "invalid_input",
66
+ operation: "posts.list",
67
+ message: "X feed limit must be an integer from 1 through 100.",
68
+ });
69
+ const result = object(await request(`/2/users/${encodeURIComponent(account.accountId)}/tweets`, context, undefined, {
70
+ "tweet.fields": "id,text,author_id,created_at,conversation_id",
71
+ ...(input.cursor === undefined ? {} : { pagination_token: input.cursor }),
72
+ ...(input.limit === undefined ? {} : { max_results: String(input.limit) }),
73
+ }));
74
+ const items = array(result["data"]).map((entry) => {
75
+ const row = object(entry);
76
+ const authorId = optionalString(row["author_id"]);
77
+ if (authorId !== account.accountId)
78
+ throw new SocialError({
79
+ code: "unauthorized",
80
+ operation: "posts.list",
81
+ message: "X returned a post owned by a different account.",
82
+ });
83
+ return publicFields(row, ["id", "text", "author_id", "created_at", "conversation_id"]);
84
+ });
85
+ const meta = result["meta"] === undefined ? {} : object(result["meta"]);
86
+ const nextCursor = optionalString(meta["next_token"]);
87
+ return {
88
+ items,
89
+ ...(nextCursor === undefined ? {} : { nextCursor }),
90
+ };
91
+ }
92
+ async function getAccountMetrics(account, context) {
93
+ authorize(account, context);
94
+ const result = object(await request(`/2/users/${encodeURIComponent(account.accountId)}`, context, undefined, {
95
+ "user.fields": "id,public_metrics",
96
+ }));
97
+ const data = object(result["data"]);
98
+ const id = optionalString(data["id"]);
99
+ if (id !== account.accountId)
100
+ throw new SocialError({
101
+ code: "unauthorized",
102
+ operation: "analytics.read",
103
+ message: "X returned metrics for a different account.",
104
+ });
105
+ const counts = data["public_metrics"] === undefined ? {} : object(data["public_metrics"]);
106
+ const fields = ["followers_count", "following_count", "tweet_count", "listed_count"];
107
+ return fields.flatMap((name) => {
108
+ const value = optionalNumber(counts[name]);
109
+ return value === undefined
110
+ ? []
111
+ : [
112
+ {
113
+ name,
114
+ value,
115
+ unit: "count",
116
+ period: "lifetime",
117
+ fetchedAt: now(),
118
+ freshness: "unknown",
119
+ source: "X API v2",
120
+ },
121
+ ];
122
+ });
123
+ }
124
+ async function validateReplyParent(ref, context) {
125
+ authorize(ref, context);
126
+ const parent = object(object(await request(`/2/tweets/${encodeURIComponent(ref.commentId)}`, context, undefined, {
127
+ "tweet.fields": "conversation_id",
128
+ }))["data"]);
129
+ if (parent["id"] !== ref.commentId)
130
+ throw new SocialError({
131
+ code: "unauthorized",
132
+ operation: "comments.write",
133
+ message: "X did not return the declared reply parent.",
134
+ });
135
+ if (ref.postId !== ref.commentId) {
136
+ const root = object(object(await request(`/2/tweets/${encodeURIComponent(ref.postId)}`, context, undefined, {
137
+ "tweet.fields": "conversation_id",
138
+ }))["data"]);
139
+ if (root["id"] !== ref.postId ||
140
+ (typeof parent["conversation_id"] === "string" &&
141
+ parent["conversation_id"] !== root["conversation_id"]))
142
+ throw new SocialError({
143
+ code: "unauthorized",
144
+ operation: "comments.write",
145
+ message: "Reply parent does not belong to the declared X conversation.",
146
+ });
147
+ }
148
+ }
149
+ async function uploadImage(media, context) {
150
+ if (media.source.kind !== "blob" ||
151
+ media.kind !== "image" ||
152
+ media.source.blob.size > 5 * 1024 * 1024)
153
+ throw new SocialError({
154
+ code: "invalid_input",
155
+ operation: "media.upload",
156
+ message: "This X slice uploads image Blobs up to 5 MiB.",
157
+ });
158
+ const body = new FormData();
159
+ body.set("media", media.source.blob, media.filename ?? "image");
160
+ body.set("media_category", "tweet_image");
161
+ let result;
162
+ try {
163
+ result = await http({
164
+ url: new URL("https://api.x.com/2/media/upload"),
165
+ method: "POST",
166
+ headers: { Authorization: `Bearer ${options.auth.accessToken}` },
167
+ body,
168
+ timeoutMs: remainingBudget(context),
169
+ ...(context.signal ? { signal: context.signal } : {}),
170
+ });
171
+ }
172
+ catch (error) {
173
+ if (!(error instanceof HttpError))
174
+ throw error;
175
+ throw new SocialError({
176
+ code: "media_error",
177
+ operation: "media.upload",
178
+ message: error.message,
179
+ upstreamStatus: error.status,
180
+ retryDisposition: { kind: "never" },
181
+ });
182
+ }
183
+ const data = object(object(result)["data"]);
184
+ if (data["processing_info"] && object(data["processing_info"])["state"] !== "succeeded")
185
+ throw new SocialError({
186
+ code: "media_error",
187
+ operation: "media.upload",
188
+ message: "X media processing is not complete. No post was created.",
189
+ });
190
+ return string(data["id"]);
191
+ }
192
+ async function createPost(account, text, context, extra = {}, targetIndex = 0) {
193
+ authorize(account, context);
194
+ const result = object(await request("/2/tweets", context, { text, ...extra }));
195
+ const data = result["data"] ? object(result["data"]) : {};
196
+ const id = optionalString(data["id"]);
197
+ const base = { account, targetIndex, observedAt: now() };
198
+ if (!id)
199
+ return {
200
+ ...base,
201
+ state: "unknown",
202
+ reason: "unmapped-state",
203
+ diagnostic: "X create response lacks a native ID. Reconcile before retrying.",
204
+ };
205
+ return {
206
+ ...base,
207
+ state: "published",
208
+ post: {
209
+ kind: "platform-post",
210
+ version: 1,
211
+ backend: account.backend,
212
+ platform: "x",
213
+ accountId: account.accountId,
214
+ postId: id,
215
+ },
216
+ };
217
+ }
218
+ return defineAdapter({
219
+ id: "x",
220
+ capabilities: {
221
+ schemaVersion: 1,
222
+ backend: "x",
223
+ apiRevision: "X API v2 / OpenAPI 2.168",
224
+ runtime: ["node22", "node24", "bun"],
225
+ capabilities: [
226
+ {
227
+ platform: "x",
228
+ operation: "posts.publish",
229
+ availability: "available",
230
+ formats: ["text", "image", "video"],
231
+ requiredScopes: ["tweet.read", "tweet.write", "users.read", "media.write"],
232
+ notes: "User-context OAuth2 token; current X API access/billing required. Up to four static JPEG/PNG image Blobs, each at most 5 MiB. Video/GIF processing is outside this slice.",
233
+ },
234
+ ...[
235
+ "accounts.read",
236
+ "posts.list",
237
+ "posts.read",
238
+ "analytics.read",
239
+ "comments.write",
240
+ "posts.repost",
241
+ "posts.quote",
242
+ "posts.delete",
243
+ "polls.create",
244
+ "bookmarks.read",
245
+ "bookmarks.write",
246
+ "follows.write",
247
+ ].map((operation) => ({
248
+ platform: "x",
249
+ operation,
250
+ availability: "available",
251
+ })),
252
+ {
253
+ platform: "x",
254
+ operation: "media.video",
255
+ availability: "available",
256
+ formats: ["video"],
257
+ },
258
+ { platform: "x", operation: "media.gif", availability: "available" },
259
+ {
260
+ platform: "x",
261
+ operation: "messages.read",
262
+ availability: "permission-required",
263
+ requiredScopes: ["dm.read"],
264
+ },
265
+ {
266
+ platform: "x",
267
+ operation: "streams.read",
268
+ availability: "approval-dependent",
269
+ notes: "Filtered stream access depends on the X API tier.",
270
+ },
271
+ ],
272
+ },
273
+ accounts: {
274
+ async list(_input, context) {
275
+ return { items: [await readAccount(context)] };
276
+ },
277
+ async get(ref, context) {
278
+ authorize(ref, context);
279
+ return readAccount(context);
280
+ },
281
+ },
282
+ posts: {
283
+ list: listPosts,
284
+ prepareTarget(target) {
285
+ const issues = [];
286
+ const fail = (code, message) => issues.push({ code, message, severity: "error", targetIndex: target.targetIndex });
287
+ if (target.account.platform !== "x" || target.account.accountId !== options.auth.userId)
288
+ fail("x.account", "Select the configured X user.");
289
+ const text = target.content.text ?? "";
290
+ if (text && !twitterText.parseTweet(text).valid)
291
+ fail("x.text", "Text exceeds X's weighted 280-character limit or contains invalid characters.");
292
+ if (target.schedule || target.content.link)
293
+ fail("x.operation", "Scheduling needs an application runner; place URLs explicitly in text.");
294
+ if (target.replyTo &&
295
+ (target.replyTo.platform !== "x" ||
296
+ target.replyTo.backend !== target.account.backend ||
297
+ target.replyTo.accountId !== target.account.accountId ||
298
+ target.replyTo.kind !== "platform-post"))
299
+ fail("x.reply", "Use a platform-post reply reference authorized for this account and backend.");
300
+ const settings = target.options === undefined ? {} : object(target.options);
301
+ if (Object.keys(settings).some((key) => key !== "replySettings") ||
302
+ (settings["replySettings"] !== undefined &&
303
+ !["everyone", "following", "mentionedUsers"].includes(String(settings["replySettings"]))))
304
+ fail("x.options", "Provide only a supported replySettings value.");
305
+ const media = target.content.media ?? [];
306
+ if (media.length > 4)
307
+ fail("x.media_count", "Attach up to four images.");
308
+ for (const item of media) {
309
+ if (item.kind !== "image" ||
310
+ item.source.kind !== "blob" ||
311
+ !["image/jpeg", "image/png"].includes(item.mimeType ?? ""))
312
+ fail("x.image", "This slice requires a JPEG or PNG image Blob.");
313
+ else if (item.source.blob.size > 5 * 1024 * 1024)
314
+ fail("x.image_size", "Image exceeds the 5 MiB limit.");
315
+ if (item.altText !== undefined)
316
+ fail("x.alt_text", "This slice has no verified media metadata mapping. Use a backend that supports alt text.");
317
+ }
318
+ return issues;
319
+ },
320
+ async publishTarget(target, context) {
321
+ authorize(target.account, context);
322
+ const ids = [];
323
+ for (const media of target.content.media ?? [])
324
+ ids.push(await uploadImage(media, context));
325
+ const settings = target.options === undefined ? {} : object(target.options);
326
+ return createPost(target.account, target.content.text ?? "", context, {
327
+ ...(ids.length ? { media: { media_ids: ids } } : {}),
328
+ ...(target.replyTo ? { reply: { in_reply_to_tweet_id: target.replyTo.postId } } : {}),
329
+ ...(settings["replySettings"] && settings["replySettings"] !== "everyone"
330
+ ? { reply_settings: string(settings["replySettings"]) }
331
+ : {}),
332
+ }, target.targetIndex);
333
+ },
334
+ async get(ref, context) {
335
+ return publicFields(await readPost(ref, context), [
336
+ "id",
337
+ "text",
338
+ "author_id",
339
+ "created_at",
340
+ "conversation_id",
341
+ ]);
342
+ },
343
+ },
344
+ comments: {
345
+ async list() {
346
+ throw new SocialError({
347
+ code: "unsupported_capability",
348
+ operation: "comments.read",
349
+ message: "X reply search is not implemented in this slice.",
350
+ });
351
+ },
352
+ async reply(ref, content, context) {
353
+ await validateReplyParent(ref, context);
354
+ if (!twitterText.parseTweet(content.text).valid)
355
+ throw new SocialError({
356
+ code: "invalid_input",
357
+ operation: "comments.write",
358
+ message: "Reply exceeds X's weighted text limits.",
359
+ });
360
+ const result = await createPost({ ...ref, kind: "connected-account" }, content.text, context, { reply: { in_reply_to_tweet_id: ref.commentId } });
361
+ if (result.state !== "published")
362
+ throw new SocialError({
363
+ code: "ambiguous_outcome",
364
+ operation: "comments.write",
365
+ message: "X reply lacks a confirmed native ID. Reconcile before retrying.",
366
+ retryDisposition: { kind: "reconcile-first" },
367
+ });
368
+ return { ...ref, commentId: result.post.postId };
369
+ },
370
+ },
371
+ analytics: {
372
+ getAccountMetrics,
373
+ async getPostMetrics(ref, context) {
374
+ const row = await readPost(ref, context);
375
+ const counts = row["public_metrics"] ? object(row["public_metrics"]) : {};
376
+ return [
377
+ ["likes", "like_count"],
378
+ ["reposts", "retweet_count"],
379
+ ["replies", "reply_count"],
380
+ ["quotes", "quote_count"],
381
+ ].flatMap(([name, key]) => {
382
+ const value = key ? optionalNumber(counts[key]) : undefined;
383
+ return value === undefined || !name
384
+ ? []
385
+ : [
386
+ {
387
+ name,
388
+ value,
389
+ unit: "count",
390
+ period: "lifetime",
391
+ fetchedAt: now(),
392
+ freshness: "unknown",
393
+ source: "X API v2",
394
+ },
395
+ ];
396
+ });
397
+ },
398
+ },
399
+ native: {
400
+ readPost,
401
+ async repost({ account, postId, context }) {
402
+ authorize(account, context);
403
+ return object(await request(`/2/users/${encodeURIComponent(account.accountId)}/retweets`, context, {
404
+ tweet_id: postId,
405
+ }));
406
+ },
407
+ async quote({ account, text, quotedPostId, context }) {
408
+ authorize(account, context);
409
+ return object(await request("/2/tweets", context, { text, quote_tweet_id: quotedPostId }));
410
+ },
411
+ async deletePost({ account, postId, context }) {
412
+ authorize(account, context);
413
+ await request(`/2/tweets/${encodeURIComponent(postId)}`, context, undefined, {}, "DELETE");
414
+ },
415
+ async createPoll({ account, text, options: pollOptions, durationMinutes, context }) {
416
+ authorize(account, context);
417
+ if (pollOptions.length < 2 ||
418
+ pollOptions.length > 4 ||
419
+ durationMinutes < 5 ||
420
+ durationMinutes > 10080)
421
+ throw new SocialError({
422
+ code: "invalid_input",
423
+ operation: "x.polls.create",
424
+ message: "X polls require 2-4 options and a duration from 5 minutes to 7 days.",
425
+ });
426
+ return object(await request("/2/tweets", context, {
427
+ text,
428
+ poll: { options: [...pollOptions], duration_minutes: durationMinutes },
429
+ }));
430
+ },
431
+ async bookmarks({ account, context }) {
432
+ authorize(account, context);
433
+ return object(await request(`/2/users/${encodeURIComponent(account.accountId)}/bookmarks`, context));
434
+ },
435
+ async bookmark({ account, postId, context }) {
436
+ authorize(account, context);
437
+ await request(`/2/users/${encodeURIComponent(account.accountId)}/bookmarks`, context, {
438
+ tweet_id: postId,
439
+ });
440
+ },
441
+ async removeBookmark({ account, postId, context }) {
442
+ authorize(account, context);
443
+ await request(`/2/users/${encodeURIComponent(account.accountId)}/bookmarks/${encodeURIComponent(postId)}`, context, undefined, {}, "DELETE");
444
+ },
445
+ async follow({ account, userId, context }) {
446
+ authorize(account, context);
447
+ return object(await request(`/2/users/${encodeURIComponent(account.accountId)}/following`, context, {
448
+ target_user_id: userId,
449
+ }));
450
+ },
451
+ async unfollow({ account, userId, context }) {
452
+ authorize(account, context);
453
+ await request(`/2/users/${encodeURIComponent(account.accountId)}/following/${encodeURIComponent(userId)}`, context, undefined, {}, "DELETE");
454
+ },
455
+ async uploadVideo({ account, media, context }) {
456
+ authorize(account, context);
457
+ if (!media.type.startsWith("video/") || media.size > 512 * 1024 * 1024)
458
+ throw new SocialError({
459
+ code: "invalid_input",
460
+ operation: "x.media.video",
461
+ message: "Provide a video Blob no larger than 512 MiB.",
462
+ });
463
+ const body = new FormData();
464
+ body.set("media", media, "video");
465
+ body.set("media_category", "tweet_video");
466
+ return object(await http({
467
+ url: new URL("https://api.x.com/2/media/upload"),
468
+ method: "POST",
469
+ headers: { Authorization: `Bearer ${options.auth.accessToken}` },
470
+ body,
471
+ timeoutMs: remainingBudget(context),
472
+ ...(context.signal ? { signal: context.signal } : {}),
473
+ }));
474
+ },
475
+ async uploadGif({ account, media, context }) {
476
+ authorize(account, context);
477
+ if (media.type !== "image/gif")
478
+ throw new SocialError({
479
+ code: "invalid_input",
480
+ operation: "x.media.gif",
481
+ message: "GIF uploads require image/gif media.",
482
+ });
483
+ const body = new FormData();
484
+ body.set("media", media, "image.gif");
485
+ body.set("media_category", "tweet_gif");
486
+ return object(await http({
487
+ url: new URL("https://api.x.com/2/media/upload"),
488
+ method: "POST",
489
+ headers: { Authorization: `Bearer ${options.auth.accessToken}` },
490
+ body,
491
+ timeoutMs: remainingBudget(context),
492
+ ...(context.signal ? { signal: context.signal } : {}),
493
+ }));
494
+ },
495
+ async listDirectMessages({ account, context }) {
496
+ authorize(account, context);
497
+ return object(await request("/2/dm_conversations", context));
498
+ },
499
+ async stream({ account, context }) {
500
+ authorize(account, context);
501
+ return (await http({
502
+ url: new URL("https://api.x.com/2/tweets/search/stream"),
503
+ headers: { Authorization: `Bearer ${options.auth.accessToken}` },
504
+ timeoutMs: remainingBudget(context),
505
+ ...(context.signal ? { signal: context.signal } : {}),
506
+ }));
507
+ },
508
+ },
509
+ });
510
+ }
511
+ //# sourceMappingURL=x.js.map