twitter-api-browser-js 0.0.3 → 0.0.5

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.
@@ -0,0 +1,9 @@
1
+ export declare const REQUEST_FUNC_GLOBAL_KEY = "elonmusk_810_request";
2
+ export declare const INITIAL_STATE_PROP_KEY = "__INITIAL_STATE__";
3
+ export declare const INITIAL_STATE_GLOBAL_KEY = "elonmusk_810_init_state";
4
+ export declare const OPERATIONS_GLOBAL_KEY = "elonmusk_810_operations";
5
+ export declare const DEFAULT_USER_DATA_DIR = "./.user_data";
6
+ export declare const METHOD_MAP: {
7
+ readonly query: "GET";
8
+ readonly mutation: "POST";
9
+ };
package/dist/consts.js ADDED
@@ -0,0 +1,9 @@
1
+ export const REQUEST_FUNC_GLOBAL_KEY = "elonmusk_810_request";
2
+ export const INITIAL_STATE_PROP_KEY = "__INITIAL_STATE__";
3
+ export const INITIAL_STATE_GLOBAL_KEY = "elonmusk_810_init_state";
4
+ export const OPERATIONS_GLOBAL_KEY = "elonmusk_810_operations";
5
+ export const DEFAULT_USER_DATA_DIR = "./.user_data";
6
+ export const METHOD_MAP = {
7
+ query: "GET",
8
+ mutation: "POST",
9
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { TwitterAPIBrowser } from "./main.ts";
2
+ import { createInterface } from "node:readline";
3
+ function prompt(question) {
4
+ const rl = createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+ return new Promise((resolve) => {
9
+ rl.question(question, (answer) => {
10
+ rl.close();
11
+ resolve(answer);
12
+ });
13
+ });
14
+ }
15
+ function formatLog(...args) {
16
+ console.dir(args, { depth: null });
17
+ }
18
+ async function main() {
19
+ const userDataDir = "./.data";
20
+ const browser = new TwitterAPIBrowser(userDataDir);
21
+ console.log("Setup browser");
22
+ await browser.setup();
23
+ try {
24
+ if (await browser.isLoggedIn()) {
25
+ console.log("User is logged in");
26
+ }
27
+ else {
28
+ console.log("User is not logged in, please login manually");
29
+ await browser.manualLogin();
30
+ }
31
+ while (true) {
32
+ console.log("=".repeat(20));
33
+ const operation = await prompt("Choose operation [CreateTweet, HomeTimeline, UserByScreenName, CreateRetweet, FavoriteTweet, SearchTimeline, UsersByRestIds, exit]: ");
34
+ if (!operation || operation === "exit") {
35
+ return;
36
+ }
37
+ const request = async () => {
38
+ if (operation === "CreateTweet") {
39
+ const res = await browser.request("CreateTweet", {
40
+ tweet_text: `Hello, World! ${new Date().toISOString()}`,
41
+ dark_request: false,
42
+ media: { media_entities: [], possibly_sensitive: false },
43
+ semantic_annotation_ids: [],
44
+ disallowed_reply_options: null,
45
+ });
46
+ return res;
47
+ }
48
+ else if (operation === "HomeTimeline") {
49
+ const res = await browser.request("HomeTimeline", {
50
+ count: 20,
51
+ includePromotedContent: true,
52
+ latestControlAvailable: true,
53
+ withCommunity: true,
54
+ });
55
+ return res;
56
+ }
57
+ else if (operation === "UserByScreenName") {
58
+ const res = await browser.request("UserByScreenName", {
59
+ screen_name: "elonmusk",
60
+ withSafetyModeUserFields: true,
61
+ withSuperFollowsUserFields: true,
62
+ withBirdwatchPivots: false,
63
+ }, {
64
+ withAuxiliaryUserLabels: true,
65
+ });
66
+ return res;
67
+ }
68
+ else if (operation === "CreateRetweet") {
69
+ const res = await browser.request("CreateRetweet", {
70
+ tweet_id: "1987547856664993831",
71
+ dark_request: false,
72
+ });
73
+ return res;
74
+ }
75
+ else if (operation === "FavoriteTweet") {
76
+ const res = await browser.request("FavoriteTweet", {
77
+ tweet_id: "1987547856664993831",
78
+ });
79
+ return res;
80
+ }
81
+ else if (operation === "SearchTimeline") {
82
+ const res = await browser.request("SearchTimeline", {
83
+ rawQuery: "from:elonmusk",
84
+ count: 20,
85
+ querySource: "typed_query",
86
+ product: "Top",
87
+ withGrokTranslatedBio: false,
88
+ });
89
+ return res;
90
+ }
91
+ else if (operation === "UsersByRestIds") {
92
+ const res = await browser.request("UsersByRestIds", {
93
+ userIds: ["900282258736545792"],
94
+ });
95
+ return res;
96
+ }
97
+ else {
98
+ console.log(`Unknown operation: ${operation}`);
99
+ }
100
+ };
101
+ const res = await request();
102
+ formatLog(res);
103
+ }
104
+ }
105
+ finally {
106
+ await browser.close();
107
+ }
108
+ }
109
+ await main();
@@ -0,0 +1,27 @@
1
+ import { METHOD_MAP } from "./consts.js";
2
+ import { LooseType } from "./twitter-types.js";
3
+ export declare const SETUP_SCRIPT = "\n(async () => {\n if (globalThis.elonmusk_810_request) {\n return;\n }\n const __origApply = Function.prototype.apply;\n const client = await new Promise((resolve) => {\n Function.prototype.apply = function (thisArg, argsArray) {\n if (thisArg && typeof thisArg === \"object\" && thisArg.dispatch === this) {\n resolve(thisArg);\n }\n return __origApply.bind(this)(thisArg, argsArray);\n };\n });\n Function.prototype.apply = __origApply;\n globalThis.elonmusk_810_request = (query) => {\n return client.dispatch.apply(client, [query]);\n };\n})();\n";
4
+ export declare const INITIAL_STATE_SCRIPT = "\n(async () => {\n const init_state_promise = new Promise((resolve) => {\n Object.defineProperty(window, \"__INITIAL_STATE__\", {\n configurable: true,\n enumerable: true,\n get() {\n return undefined;\n },\n set(v) {\n resolve(v);\n Object.defineProperty(window, \"__INITIAL_STATE__\", {\n value: v,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n },\n });\n });\n\n globalThis.elonmusk_810_init_state = init_state_promise;\n})();\n";
5
+ export declare const OPERATIONS_SCRIPT = "\n(async () => {\n globalThis.elonmusk_810_operations = [];\n const origCall = Function.prototype.call;\n Function.prototype.call = function (thisArg, ...args) {\n const module = args[0];\n const ret = origCall.bind(this)(thisArg, ...args);\n try {\n const exp = module.exports;\n if (exp.operationName) {\n globalThis.elonmusk_810_operations.push(exp);\n }\n } catch (_) {}\n return ret;\n };\n await new Promise((resolve) => setTimeout(resolve, 5000));\n Function.prototype.call = origCall;\n})();\n";
6
+ export type Operation = {
7
+ operationName: string;
8
+ queryId: string;
9
+ operationType: keyof typeof METHOD_MAP;
10
+ metadata: {
11
+ featureSwitches: string[];
12
+ allowFieldToggles: string[];
13
+ fieldToggles: string[];
14
+ };
15
+ } & Record<string, LooseType>;
16
+ type FeatureSwitchValue = {
17
+ value: boolean;
18
+ } & Record<string, LooseType>;
19
+ export type InitialState = {
20
+ featureSwitch: {
21
+ defaultConfig: Record<string, FeatureSwitchValue>;
22
+ user: Record<string, FeatureSwitchValue>;
23
+ debug: Record<string, FeatureSwitchValue>;
24
+ customOverrides: Record<string, FeatureSwitchValue>;
25
+ };
26
+ } & Record<string, LooseType>;
27
+ export {};
package/dist/inject.js ADDED
@@ -0,0 +1,64 @@
1
+ import { INITIAL_STATE_GLOBAL_KEY, INITIAL_STATE_PROP_KEY, OPERATIONS_GLOBAL_KEY, REQUEST_FUNC_GLOBAL_KEY, } from "./consts.js";
2
+ export const SETUP_SCRIPT = `
3
+ (async () => {
4
+ if (globalThis.${REQUEST_FUNC_GLOBAL_KEY}) {
5
+ return;
6
+ }
7
+ const __origApply = Function.prototype.apply;
8
+ const client = await new Promise((resolve) => {
9
+ Function.prototype.apply = function (thisArg, argsArray) {
10
+ if (thisArg && typeof thisArg === "object" && thisArg.dispatch === this) {
11
+ resolve(thisArg);
12
+ }
13
+ return __origApply.bind(this)(thisArg, argsArray);
14
+ };
15
+ });
16
+ Function.prototype.apply = __origApply;
17
+ globalThis.${REQUEST_FUNC_GLOBAL_KEY} = (query) => {
18
+ return client.dispatch.apply(client, [query]);
19
+ };
20
+ })();
21
+ `;
22
+ export const INITIAL_STATE_SCRIPT = `
23
+ (async () => {
24
+ const init_state_promise = new Promise((resolve) => {
25
+ Object.defineProperty(window, "${INITIAL_STATE_PROP_KEY}", {
26
+ configurable: true,
27
+ enumerable: true,
28
+ get() {
29
+ return undefined;
30
+ },
31
+ set(v) {
32
+ resolve(v);
33
+ Object.defineProperty(window, "${INITIAL_STATE_PROP_KEY}", {
34
+ value: v,
35
+ writable: true,
36
+ enumerable: true,
37
+ configurable: true,
38
+ });
39
+ },
40
+ });
41
+ });
42
+
43
+ globalThis.${INITIAL_STATE_GLOBAL_KEY} = init_state_promise;
44
+ })();
45
+ `;
46
+ export const OPERATIONS_SCRIPT = `
47
+ (async () => {
48
+ globalThis.${OPERATIONS_GLOBAL_KEY} = [];
49
+ const origCall = Function.prototype.call;
50
+ Function.prototype.call = function (thisArg, ...args) {
51
+ const module = args[0];
52
+ const ret = origCall.bind(this)(thisArg, ...args);
53
+ try {
54
+ const exp = module.exports;
55
+ if (exp.operationName) {
56
+ globalThis.${OPERATIONS_GLOBAL_KEY}.push(exp);
57
+ }
58
+ } catch (_) {}
59
+ return ret;
60
+ };
61
+ await new Promise((resolve) => setTimeout(resolve, 5000));
62
+ Function.prototype.call = origCall;
63
+ })();
64
+ `;
package/dist/main.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { LooseErrorResponse, LooseType, SuccessResponse, TwitterOpenAPIModelsMapping } from "./twitter-types.ts";
2
+ /**
3
+ * @classdesc Base class for operating Twitter API Browser
4
+ */
5
+ export declare class TwitterAPIBrowser {
6
+ private readonly userDataDir;
7
+ /**
8
+ * @description Constructor for TwitterAPIBrowser.
9
+ * @param userDataDir - The directory to store the user data for the browser (example: "./.user_data")
10
+ */
11
+ constructor(userDataDir?: string);
12
+ private browser;
13
+ private page;
14
+ private operations;
15
+ private initialState;
16
+ /**
17
+ * @description Launches a new browser context and page, and then injects scripts.
18
+ * @param waitForReady - The number of seconds to wait for the browser to be ready.
19
+ * @param headless - Whether to run the browser in headless mode. (Recommended: false)
20
+ */
21
+ setup(waitForReady?: number, headless?: boolean): Promise<void>;
22
+ /**
23
+ * @description Closes the browser context and page.
24
+ */
25
+ close(): Promise<void>;
26
+ [Symbol.asyncDispose]: () => Promise<void>;
27
+ /**
28
+ * @description Is user logged in?
29
+ */
30
+ isLoggedIn(): Promise<boolean>;
31
+ /**
32
+ * @description Manually login to Twitter.
33
+ */
34
+ manualLogin(): Promise<void>;
35
+ /**
36
+ * @description Send a GraphQL request to the Twitter API
37
+ * @param method - The method of the request
38
+ * @param path - The path of the request
39
+ * @param body - The body of the request
40
+ * @returns Response of the request
41
+ */
42
+ graphql<T extends string = string>(method: string, path: string, body: LooseType): Promise<SuccessResponse<T> | LooseErrorResponse>;
43
+ /**
44
+ * Sends a request to the Twitter API
45
+ * @param operationName - The name of the operation to request
46
+ * @param variables - The variables to pass to the operation
47
+ * @param fieldToggles - The fields to toggle on or off in the operation
48
+ * @returns Response of the request
49
+ */
50
+ request<T extends keyof TwitterOpenAPIModelsMapping = string>(operationName: T, variables: LooseType, fieldToggles?: Record<string, boolean>): Promise<SuccessResponse<T> | LooseErrorResponse>;
51
+ }
package/dist/main.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Twitter API Browser for JavaScript
3
+ * @module
4
+ * @exports TwitterAPIBrowser
5
+ */
6
+ import * as fs from "node:fs";
7
+ import { INITIAL_STATE_SCRIPT, OPERATIONS_SCRIPT, SETUP_SCRIPT, } from "./inject.js";
8
+ import { DEFAULT_USER_DATA_DIR, INITIAL_STATE_GLOBAL_KEY, METHOD_MAP, OPERATIONS_GLOBAL_KEY, REQUEST_FUNC_GLOBAL_KEY, } from "./consts.js";
9
+ import { chromium } from "playwright";
10
+ import { pickFirstItem, removeNullRecursively, sleep } from "./utils.js";
11
+ /**
12
+ * @classdesc Base class for operating Twitter API Browser
13
+ */
14
+ export class TwitterAPIBrowser {
15
+ userDataDir;
16
+ /**
17
+ * @description Constructor for TwitterAPIBrowser.
18
+ * @param userDataDir - The directory to store the user data for the browser (example: "./.user_data")
19
+ */
20
+ constructor(userDataDir = DEFAULT_USER_DATA_DIR) {
21
+ this.userDataDir = userDataDir;
22
+ if (!fs.existsSync(userDataDir)) {
23
+ fs.mkdirSync(userDataDir, { recursive: true });
24
+ }
25
+ }
26
+ browser;
27
+ page;
28
+ operations;
29
+ initialState;
30
+ /**
31
+ * @description Launches a new browser context and page, and then injects scripts.
32
+ * @param waitForReady - The number of seconds to wait for the browser to be ready.
33
+ * @param headless - Whether to run the browser in headless mode. (Recommended: false)
34
+ */
35
+ async setup(waitForReady = 5, headless = false) {
36
+ if (this.browser && this.page) {
37
+ await this.close();
38
+ }
39
+ const { resolve, reject, promise: browserPromise, } = Promise.withResolvers();
40
+ chromium
41
+ .launchPersistentContext(this.userDataDir, {
42
+ headless: headless,
43
+ viewport: null,
44
+ args: [
45
+ "--disable-blink-features=AutomationControlled",
46
+ "--no-sandbox",
47
+ "--disable-dev-shm-usage",
48
+ "--disable-gpu",
49
+ ],
50
+ })
51
+ .then(resolve)
52
+ .catch(reject);
53
+ const raceResult = await Promise.race([
54
+ browserPromise,
55
+ sleep(waitForReady * 1000, new Error("Browser launch timed out")),
56
+ ]);
57
+ if (raceResult instanceof Error) {
58
+ throw raceResult;
59
+ }
60
+ this.browser = raceResult;
61
+ this.page = await this.browser.newPage();
62
+ await this.page.addInitScript(SETUP_SCRIPT);
63
+ await this.page.addInitScript(OPERATIONS_SCRIPT);
64
+ await this.page.addInitScript(INITIAL_STATE_SCRIPT);
65
+ await this.page.goto("https://x.com/home");
66
+ await sleep(waitForReady * 1000);
67
+ this.operations = await this.page.evaluate(`globalThis.${OPERATIONS_GLOBAL_KEY}`);
68
+ this.initialState = await this.page.evaluate(`globalThis.${INITIAL_STATE_GLOBAL_KEY}`);
69
+ }
70
+ /**
71
+ * @description Closes the browser context and page.
72
+ */
73
+ async close() {
74
+ if (this.browser && this.page) {
75
+ await this.page.close();
76
+ await this.browser.close();
77
+ }
78
+ // NOTE: Patch memory leak
79
+ this.browser = undefined;
80
+ this.page = undefined;
81
+ this.operations = undefined;
82
+ this.initialState = undefined;
83
+ }
84
+ [Symbol.asyncDispose] = this.close;
85
+ /**
86
+ * @description Is user logged in?
87
+ */
88
+ async isLoggedIn() {
89
+ const cookies = await this.browser?.cookies("https://x.com");
90
+ if (!cookies) {
91
+ return false;
92
+ }
93
+ return cookies.some((cookie) => cookie.name === "auth_token");
94
+ }
95
+ /**
96
+ * @description Manually login to Twitter.
97
+ */
98
+ async manualLogin() {
99
+ if (!this.page) {
100
+ throw new Error("Maybe you forgot to call setup()?");
101
+ }
102
+ await this.page.goto("https://x.com/login");
103
+ await sleep(1000);
104
+ // NOTE: Wait for the page to load the home page after login
105
+ await this.page.waitForURL("https://x.com/home", {
106
+ timeout: 0,
107
+ });
108
+ }
109
+ /**
110
+ * @description Send a GraphQL request to the Twitter API
111
+ * @param method - The method of the request
112
+ * @param path - The path of the request
113
+ * @param body - The body of the request
114
+ * @returns Response of the request
115
+ */
116
+ async graphql(method, path, body) {
117
+ if (!this.page) {
118
+ throw new Error("Maybe you forgot to call setup()?");
119
+ }
120
+ const args = {
121
+ headers: {
122
+ "content-type": "application/json",
123
+ },
124
+ method,
125
+ path,
126
+ params: null,
127
+ data: null,
128
+ };
129
+ if (method === "GET") {
130
+ const params = Object.fromEntries(Object.entries(body).map(([k, v]) => [k, JSON.stringify(v)]));
131
+ args["params"] = {
132
+ queryId: body.queryId,
133
+ variables: JSON.stringify(body.variables),
134
+ };
135
+ if (body.features) {
136
+ args["params"]["features"] = JSON.stringify(body.features);
137
+ }
138
+ if (body.fieldToggle) {
139
+ args["params"]["fieldToggle"] = JSON.stringify(body.fieldToggle);
140
+ }
141
+ }
142
+ else if (method === "POST") {
143
+ args["data"] = body;
144
+ }
145
+ await this.page.evaluate(SETUP_SCRIPT);
146
+ await sleep(500);
147
+ const response = [await this.page.evaluate(`globalThis.${REQUEST_FUNC_GLOBAL_KEY}(${JSON.stringify(removeNullRecursively(args))})`)].flat();
148
+ const result = pickFirstItem(response, "response");
149
+ if (result instanceof Error) {
150
+ console.log(`!!! Please report this error to the developer !!!`);
151
+ throw result;
152
+ }
153
+ // TODO: more strict type check
154
+ return result;
155
+ }
156
+ /**
157
+ * Sends a request to the Twitter API
158
+ * @param operationName - The name of the operation to request
159
+ * @param variables - The variables to pass to the operation
160
+ * @param fieldToggles - The fields to toggle on or off in the operation
161
+ * @returns Response of the request
162
+ */
163
+ async request(operationName, variables, fieldToggles = {}) {
164
+ if (!this.page || !this.operations || !this.initialState) {
165
+ throw new Error("Maybe you forgot to call setup()?");
166
+ }
167
+ const exp = pickFirstItem(this.operations.filter((x) => x.operationName === operationName) ?? [], "operation");
168
+ if (exp instanceof Error) {
169
+ throw exp;
170
+ }
171
+ const queryId = exp.queryId;
172
+ const operationType = exp.operationType;
173
+ const featureSwitches = exp.metadata.featureSwitches;
174
+ const allowFieldToggles = exp.metadata.allowFieldToggles;
175
+ const fieldToggle = {};
176
+ for (const [k, v] of Object.entries(fieldToggles)) {
177
+ if (allowFieldToggles && allowFieldToggles.includes(k)) {
178
+ fieldToggle[k] = v;
179
+ }
180
+ }
181
+ const method = METHOD_MAP[operationType];
182
+ const featureSwitch = {
183
+ ...this.initialState.featureSwitch.defaultConfig,
184
+ ...this.initialState.featureSwitch.user,
185
+ ...this.initialState.featureSwitch.debug,
186
+ ...this.initialState.featureSwitch.customOverrides,
187
+ };
188
+ const featureSwitchesMap = Object.fromEntries(Object.entries(featureSwitch).filter(([k]) => featureSwitches.includes(k)).map(([k, v]) => [k, v.value]));
189
+ const body = {
190
+ variables: variables,
191
+ queryId: queryId,
192
+ features: null,
193
+ fieldToggle: null,
194
+ };
195
+ if (featureSwitchesMap && Object.keys(featureSwitchesMap).length > 0) {
196
+ body.features = featureSwitchesMap;
197
+ }
198
+ if (fieldToggle && Object.keys(fieldToggle).length > 0) {
199
+ body.fieldToggle = fieldToggle;
200
+ }
201
+ return await this.graphql(method, `/graphql/${queryId}/${operationName}`, body);
202
+ }
203
+ }
@@ -0,0 +1,16 @@
1
+ import type * as TwitterOpenAPIModels from "twitter-openapi-typescript-generated/dist/models/index.d.ts";
2
+ export type LooseType = any;
3
+ export type TwitterOpenAPIModelsMapping = {
4
+ "CreateTweet": TwitterOpenAPIModels.CreateTweet;
5
+ "HomeTimeline": TwitterOpenAPIModels.HomeTimelineHome;
6
+ "UserByScreenName": TwitterOpenAPIModels.UserResultByScreenName;
7
+ "CreateRetweet": TwitterOpenAPIModels.CreateRetweet;
8
+ "FavoriteTweet": TwitterOpenAPIModels.FavoriteTweet;
9
+ "SearchTimeline": TwitterOpenAPIModels.SearchTimeline;
10
+ "UsersByRestIds": TwitterOpenAPIModels.UsersResponse;
11
+ } & Record<string, LooseType>;
12
+ export type SuccessResponse<T extends string> = TwitterOpenAPIModelsMapping[T];
13
+ export type LooseErrorResponse = {
14
+ erros: TwitterOpenAPIModels.ErrorResponse[];
15
+ data: {};
16
+ } & Record<string, LooseType>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare const pickFirstItem: <T>(values: T[], itemName?: string) => T | Error;
2
+ export declare const sleep: <T>(ms: number, value?: T) => Promise<T>;
3
+ type RemoveNullRecursively<T> = T extends null ? never : T extends (infer U)[] ? RemoveNullRecursively<U>[] : T extends Record<string, unknown> ? {
4
+ [K in keyof T as T[K] extends null ? never : K]: RemoveNullRecursively<T[K]>;
5
+ } : T;
6
+ export declare const removeNullRecursively: <T>(obj: T) => RemoveNullRecursively<T>;
7
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,35 @@
1
+ export const pickFirstItem = (values, itemName = "item") => {
2
+ if (values.length === 0) {
3
+ return new Error(`No ${itemName} provided`);
4
+ }
5
+ else if (values.length > 1) {
6
+ return new Error(`Multiple ${itemName} provided`);
7
+ }
8
+ return values[0];
9
+ };
10
+ export const sleep = (ms, value = undefined) => {
11
+ return new Promise((resolve) => setTimeout(() => resolve(value), ms));
12
+ };
13
+ export const removeNullRecursively = (obj) => {
14
+ if (obj === null || obj === undefined) {
15
+ return obj;
16
+ }
17
+ if (Array.isArray(obj)) {
18
+ return obj
19
+ .map((item) => removeNullRecursively(item))
20
+ .filter((item) => item !== null);
21
+ }
22
+ if (typeof obj === "object") {
23
+ const result = {};
24
+ for (const [key, value] of Object.entries(obj)) {
25
+ if (value !== null) {
26
+ const cleanedValue = removeNullRecursively(value);
27
+ if (cleanedValue !== null) {
28
+ result[key] = cleanedValue;
29
+ }
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ return obj;
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twitter-api-browser-js",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -8,6 +8,7 @@
8
8
  "types": "./dist/index.d.ts"
9
9
  }
10
10
  },
11
+ "types": "./dist/index.d.ts",
11
12
  "dependencies": {
12
13
  "playwright": "npm:patchright@^1.57.0",
13
14
  "twitter-openapi-typescript-generated": "^0.0.38"