spora 0.3.2 → 0.3.3
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/package.json +1 -1
- package/dist/account-creator-SETL5CGT.js +0 -498
- package/dist/account-creator-SETL5CGT.js.map +0 -1
- package/dist/chunk-DFSYD45Q.js +0 -665
- package/dist/chunk-DFSYD45Q.js.map +0 -1
- package/dist/chunk-FCAK5FYQ.js +0 -127
- package/dist/chunk-FCAK5FYQ.js.map +0 -1
- package/dist/chunk-GJFBWIW3.js +0 -622
- package/dist/chunk-GJFBWIW3.js.map +0 -1
- package/dist/chunk-HERI4RPY.js +0 -156
- package/dist/chunk-HERI4RPY.js.map +0 -1
- package/dist/chunk-J7J557HV.js +0 -47
- package/dist/chunk-J7J557HV.js.map +0 -1
- package/dist/chunk-JWMADEQO.js +0 -57
- package/dist/chunk-JWMADEQO.js.map +0 -1
- package/dist/chunk-LRKBNKMQ.js +0 -79
- package/dist/chunk-LRKBNKMQ.js.map +0 -1
- package/dist/chunk-NLWU5432.js +0 -32
- package/dist/chunk-NLWU5432.js.map +0 -1
- package/dist/chunk-POEDIDM6.js +0 -56
- package/dist/chunk-POEDIDM6.js.map +0 -1
- package/dist/chunk-Q7YS3AIK.js +0 -63
- package/dist/chunk-Q7YS3AIK.js.map +0 -1
- package/dist/chunk-QHFM2YW6.js +0 -159
- package/dist/chunk-QHFM2YW6.js.map +0 -1
- package/dist/chunk-R7PAD4OL.js +0 -44
- package/dist/chunk-R7PAD4OL.js.map +0 -1
- package/dist/chunk-RNVEWVDN.js +0 -129
- package/dist/chunk-RNVEWVDN.js.map +0 -1
- package/dist/chunk-SUFTVQME.js +0 -82
- package/dist/chunk-SUFTVQME.js.map +0 -1
- package/dist/chunk-SXMDYUK3.js +0 -80
- package/dist/chunk-SXMDYUK3.js.map +0 -1
- package/dist/chunk-YZ7RWJ6Z.js +0 -262
- package/dist/chunk-YZ7RWJ6Z.js.map +0 -1
- package/dist/cli.js +0 -654
- package/dist/cli.js.map +0 -1
- package/dist/client-23THPNVL.js +0 -382
- package/dist/client-23THPNVL.js.map +0 -1
- package/dist/client-NVI3ZD4G.js +0 -411
- package/dist/client-NVI3ZD4G.js.map +0 -1
- package/dist/colony-J4EZQI37.js +0 -229
- package/dist/colony-J4EZQI37.js.map +0 -1
- package/dist/config-QRBOL4NX.js +0 -14
- package/dist/config-QRBOL4NX.js.map +0 -1
- package/dist/crypto-ZVWJLD2J.js +0 -14
- package/dist/crypto-ZVWJLD2J.js.map +0 -1
- package/dist/decision-engine-WBD36PZI.js +0 -19
- package/dist/decision-engine-WBD36PZI.js.map +0 -1
- package/dist/goals-IM4AEHS4.js +0 -12
- package/dist/goals-IM4AEHS4.js.map +0 -1
- package/dist/heartbeat-35HVB5PB.js +0 -317
- package/dist/heartbeat-35HVB5PB.js.map +0 -1
- package/dist/identity-LN2R4KJU.js +0 -26
- package/dist/identity-LN2R4KJU.js.map +0 -1
- package/dist/image-search-SZVMGWLN.js +0 -45
- package/dist/image-search-SZVMGWLN.js.map +0 -1
- package/dist/init-ANGLSI2L.js +0 -403
- package/dist/init-ANGLSI2L.js.map +0 -1
- package/dist/llm-MHZG2VHU.js +0 -16
- package/dist/llm-MHZG2VHU.js.map +0 -1
- package/dist/mcp-server.js +0 -773
- package/dist/mcp-server.js.map +0 -1
- package/dist/memory-J6AYZ5Y2.js +0 -26
- package/dist/memory-J6AYZ5Y2.js.map +0 -1
- package/dist/memory-JMXU3UXR.js +0 -26
- package/dist/memory-JMXU3UXR.js.map +0 -1
- package/dist/paths-KXOWF2B2.js +0 -13
- package/dist/paths-KXOWF2B2.js.map +0 -1
- package/dist/performance-7G6R6ELJ.js +0 -18
- package/dist/performance-7G6R6ELJ.js.map +0 -1
- package/dist/prompt-builder-NSU4IFPB.js +0 -28
- package/dist/prompt-builder-NSU4IFPB.js.map +0 -1
- package/dist/queue-MLRTMJRE.js +0 -14
- package/dist/queue-MLRTMJRE.js.map +0 -1
- package/dist/strategy-TOVFBIZQ.js +0 -12
- package/dist/strategy-TOVFBIZQ.js.map +0 -1
- package/dist/web-chat/chat.html +0 -1343
- package/dist/web-chat/logo.png +0 -0
- package/dist/web-chat-N2AYUWT7.js +0 -802
- package/dist/web-chat-N2AYUWT7.js.map +0 -1
- package/dist/x-client-HUXCQOAW.js +0 -12
- package/dist/x-client-HUXCQOAW.js.map +0 -1
package/dist/client-NVI3ZD4G.js
DELETED
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
rateLimiter
|
|
3
|
-
} from "./chunk-JWMADEQO.js";
|
|
4
|
-
import "./chunk-SXMDYUK3.js";
|
|
5
|
-
import {
|
|
6
|
-
identityExists,
|
|
7
|
-
loadIdentity
|
|
8
|
-
} from "./chunk-GJFBWIW3.js";
|
|
9
|
-
import {
|
|
10
|
-
loadCredentials
|
|
11
|
-
} from "./chunk-POEDIDM6.js";
|
|
12
|
-
import {
|
|
13
|
-
logger
|
|
14
|
-
} from "./chunk-J7J557HV.js";
|
|
15
|
-
import {
|
|
16
|
-
logInteraction
|
|
17
|
-
} from "./chunk-RNVEWVDN.js";
|
|
18
|
-
import {
|
|
19
|
-
ensureDirectories,
|
|
20
|
-
paths
|
|
21
|
-
} from "./chunk-Q7YS3AIK.js";
|
|
22
|
-
|
|
23
|
-
// src/x-client/browser/client.ts
|
|
24
|
-
import { chromium } from "playwright";
|
|
25
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
26
|
-
var X_BASE = "https://x.com";
|
|
27
|
-
var XBrowserClient = class {
|
|
28
|
-
browser = null;
|
|
29
|
-
context = null;
|
|
30
|
-
page = null;
|
|
31
|
-
async ensureBrowser() {
|
|
32
|
-
if (this.page && !this.page.isClosed()) return this.page;
|
|
33
|
-
this.browser = await chromium.launch({ headless: true });
|
|
34
|
-
if (existsSync(paths.browserAuth)) {
|
|
35
|
-
const storageState = JSON.parse(readFileSync(paths.browserAuth, "utf-8"));
|
|
36
|
-
this.context = await this.browser.newContext({ storageState });
|
|
37
|
-
} else {
|
|
38
|
-
this.context = await this.browser.newContext();
|
|
39
|
-
}
|
|
40
|
-
this.page = await this.context.newPage();
|
|
41
|
-
await this.page.goto(X_BASE, { waitUntil: "domcontentloaded" });
|
|
42
|
-
const isLoggedIn = await this.page.locator('[data-testid="SideNav_AccountSwitcher_Button"]').isVisible({ timeout: 5e3 }).catch(() => false);
|
|
43
|
-
if (!isLoggedIn) {
|
|
44
|
-
await this.login();
|
|
45
|
-
}
|
|
46
|
-
return this.page;
|
|
47
|
-
}
|
|
48
|
-
async login() {
|
|
49
|
-
const creds = loadCredentials();
|
|
50
|
-
if (!creds.username || !creds.password) {
|
|
51
|
-
throw new Error("Browser mode requires username and password credentials");
|
|
52
|
-
}
|
|
53
|
-
const page = this.page;
|
|
54
|
-
await page.goto(`${X_BASE}/login`, { waitUntil: "domcontentloaded" });
|
|
55
|
-
await page.waitForTimeout(2e3);
|
|
56
|
-
const usernameInput = page.locator('input[autocomplete="username"]');
|
|
57
|
-
await usernameInput.waitFor({ timeout: 1e4 });
|
|
58
|
-
await usernameInput.fill(creds.username);
|
|
59
|
-
await page.locator("text=Next").click();
|
|
60
|
-
await page.waitForTimeout(2e3);
|
|
61
|
-
const emailInput = page.locator('input[data-testid="ocfEnterTextTextInput"]');
|
|
62
|
-
const emailVisible = await emailInput.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
63
|
-
if (emailVisible && creds.email) {
|
|
64
|
-
await emailInput.fill(creds.email);
|
|
65
|
-
await page.locator("text=Next").click();
|
|
66
|
-
await page.waitForTimeout(2e3);
|
|
67
|
-
}
|
|
68
|
-
const passwordInput = page.locator('input[type="password"]');
|
|
69
|
-
await passwordInput.waitFor({ timeout: 1e4 });
|
|
70
|
-
await passwordInput.fill(creds.password);
|
|
71
|
-
await page.locator('[data-testid="LoginForm_Login_Button"]').click();
|
|
72
|
-
await page.waitForTimeout(3e3);
|
|
73
|
-
await this.saveSession();
|
|
74
|
-
logger.info("Logged into X via browser");
|
|
75
|
-
}
|
|
76
|
-
async saveSession() {
|
|
77
|
-
if (!this.context) return;
|
|
78
|
-
ensureDirectories();
|
|
79
|
-
const state = await this.context.storageState();
|
|
80
|
-
writeFileSync(paths.browserAuth, JSON.stringify(state));
|
|
81
|
-
}
|
|
82
|
-
getHandle() {
|
|
83
|
-
if (identityExists()) {
|
|
84
|
-
return loadIdentity().handle;
|
|
85
|
-
}
|
|
86
|
-
const creds = loadCredentials();
|
|
87
|
-
if (creds.username) return creds.username;
|
|
88
|
-
throw new Error("No handle found. Create a Spore identity first.");
|
|
89
|
-
}
|
|
90
|
-
async waitForNavigation(page, timeout = 5e3) {
|
|
91
|
-
await page.waitForTimeout(timeout);
|
|
92
|
-
}
|
|
93
|
-
async postTweet(content) {
|
|
94
|
-
if (!rateLimiter.canPost()) {
|
|
95
|
-
return { success: false, error: "Monthly post limit reached" };
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const page = await this.ensureBrowser();
|
|
99
|
-
await page.goto(X_BASE, { waitUntil: "domcontentloaded" });
|
|
100
|
-
await page.waitForTimeout(2e3);
|
|
101
|
-
const composeButton = page.locator('[data-testid="tweetButtonInline"]').first();
|
|
102
|
-
const composeArea = page.locator('[data-testid="tweetTextarea_0"]').first();
|
|
103
|
-
const areaVisible = await composeArea.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
104
|
-
if (!areaVisible) {
|
|
105
|
-
await page.locator('[data-testid="SideNav_NewTweet_Button"]').click();
|
|
106
|
-
await page.waitForTimeout(1e3);
|
|
107
|
-
}
|
|
108
|
-
const textarea = page.locator('[data-testid="tweetTextarea_0"]').first();
|
|
109
|
-
await textarea.waitFor({ timeout: 5e3 });
|
|
110
|
-
await textarea.fill(content);
|
|
111
|
-
await page.waitForTimeout(500);
|
|
112
|
-
await page.locator('[data-testid="tweetButton"]').click();
|
|
113
|
-
await page.waitForTimeout(3e3);
|
|
114
|
-
rateLimiter.consume();
|
|
115
|
-
logInteraction({
|
|
116
|
-
id: `int-${Date.now()}`,
|
|
117
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
-
type: "post",
|
|
119
|
-
content,
|
|
120
|
-
creditsUsed: 1,
|
|
121
|
-
success: true
|
|
122
|
-
});
|
|
123
|
-
await this.saveSession();
|
|
124
|
-
return { success: true };
|
|
125
|
-
} catch (error) {
|
|
126
|
-
logger.error("Failed to post tweet via browser", error);
|
|
127
|
-
return { success: false, error: error.message };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
async postTweetWithMedia(content, _mediaBuffer) {
|
|
131
|
-
logger.warn("Browser client does not support media upload, posting without image");
|
|
132
|
-
return this.postTweet(content);
|
|
133
|
-
}
|
|
134
|
-
async replyToTweetWithMedia(tweetId, content, _mediaBuffer) {
|
|
135
|
-
logger.warn("Browser client does not support media upload, replying without image");
|
|
136
|
-
return this.replyToTweet(tweetId, content);
|
|
137
|
-
}
|
|
138
|
-
async replyToTweet(tweetId, content) {
|
|
139
|
-
if (!rateLimiter.canPost()) {
|
|
140
|
-
return { success: false, error: "Monthly post limit reached" };
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
const page = await this.ensureBrowser();
|
|
144
|
-
await page.goto(`${X_BASE}/i/status/${tweetId}`, {
|
|
145
|
-
waitUntil: "domcontentloaded"
|
|
146
|
-
});
|
|
147
|
-
await page.waitForTimeout(2e3);
|
|
148
|
-
const replyArea = page.locator('[data-testid="tweetTextarea_0"]').first();
|
|
149
|
-
await replyArea.waitFor({ timeout: 5e3 });
|
|
150
|
-
await replyArea.fill(content);
|
|
151
|
-
await page.waitForTimeout(500);
|
|
152
|
-
await page.locator('[data-testid="tweetButton"]').click();
|
|
153
|
-
await page.waitForTimeout(3e3);
|
|
154
|
-
rateLimiter.consume();
|
|
155
|
-
logInteraction({
|
|
156
|
-
id: `int-${Date.now()}`,
|
|
157
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
-
type: "reply",
|
|
159
|
-
inReplyTo: tweetId,
|
|
160
|
-
content,
|
|
161
|
-
creditsUsed: 1,
|
|
162
|
-
success: true
|
|
163
|
-
});
|
|
164
|
-
await this.saveSession();
|
|
165
|
-
return { success: true };
|
|
166
|
-
} catch (error) {
|
|
167
|
-
logger.error("Failed to reply via browser", error);
|
|
168
|
-
return { success: false, error: error.message };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async deleteTweet(tweetId) {
|
|
172
|
-
try {
|
|
173
|
-
const page = await this.ensureBrowser();
|
|
174
|
-
const handle = this.getHandle();
|
|
175
|
-
await page.goto(`${X_BASE}/${handle}/status/${tweetId}`, {
|
|
176
|
-
waitUntil: "domcontentloaded"
|
|
177
|
-
});
|
|
178
|
-
await page.waitForTimeout(2e3);
|
|
179
|
-
await page.locator('[data-testid="caret"]').first().click();
|
|
180
|
-
await page.waitForTimeout(1e3);
|
|
181
|
-
await page.locator("text=Delete").click();
|
|
182
|
-
await page.waitForTimeout(1e3);
|
|
183
|
-
await page.locator('[data-testid="confirmationSheetConfirm"]').click();
|
|
184
|
-
await page.waitForTimeout(2e3);
|
|
185
|
-
await this.saveSession();
|
|
186
|
-
return { success: true, tweetId };
|
|
187
|
-
} catch (error) {
|
|
188
|
-
return { success: false, error: error.message };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async likeTweet(tweetId) {
|
|
192
|
-
try {
|
|
193
|
-
const page = await this.ensureBrowser();
|
|
194
|
-
await page.goto(`${X_BASE}/i/status/${tweetId}`, {
|
|
195
|
-
waitUntil: "domcontentloaded"
|
|
196
|
-
});
|
|
197
|
-
await page.waitForTimeout(2e3);
|
|
198
|
-
await page.locator('[data-testid="like"]').first().click();
|
|
199
|
-
await page.waitForTimeout(1e3);
|
|
200
|
-
logInteraction({
|
|
201
|
-
id: `int-${Date.now()}`,
|
|
202
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
203
|
-
type: "like",
|
|
204
|
-
tweetId,
|
|
205
|
-
creditsUsed: 0,
|
|
206
|
-
success: true
|
|
207
|
-
});
|
|
208
|
-
await this.saveSession();
|
|
209
|
-
return { success: true, tweetId };
|
|
210
|
-
} catch (error) {
|
|
211
|
-
return { success: false, error: error.message };
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
async unlikeTweet(tweetId) {
|
|
215
|
-
try {
|
|
216
|
-
const page = await this.ensureBrowser();
|
|
217
|
-
await page.goto(`${X_BASE}/i/status/${tweetId}`, {
|
|
218
|
-
waitUntil: "domcontentloaded"
|
|
219
|
-
});
|
|
220
|
-
await page.waitForTimeout(2e3);
|
|
221
|
-
await page.locator('[data-testid="unlike"]').first().click();
|
|
222
|
-
await page.waitForTimeout(1e3);
|
|
223
|
-
await this.saveSession();
|
|
224
|
-
return { success: true, tweetId };
|
|
225
|
-
} catch (error) {
|
|
226
|
-
return { success: false, error: error.message };
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
async retweet(tweetId) {
|
|
230
|
-
try {
|
|
231
|
-
const page = await this.ensureBrowser();
|
|
232
|
-
await page.goto(`${X_BASE}/i/status/${tweetId}`, {
|
|
233
|
-
waitUntil: "domcontentloaded"
|
|
234
|
-
});
|
|
235
|
-
await page.waitForTimeout(2e3);
|
|
236
|
-
await page.locator('[data-testid="retweet"]').first().click();
|
|
237
|
-
await page.waitForTimeout(1e3);
|
|
238
|
-
await page.locator('[data-testid="retweetConfirm"]').click();
|
|
239
|
-
await page.waitForTimeout(1e3);
|
|
240
|
-
logInteraction({
|
|
241
|
-
id: `int-${Date.now()}`,
|
|
242
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
243
|
-
type: "retweet",
|
|
244
|
-
tweetId,
|
|
245
|
-
creditsUsed: 0,
|
|
246
|
-
success: true
|
|
247
|
-
});
|
|
248
|
-
await this.saveSession();
|
|
249
|
-
return { success: true, tweetId };
|
|
250
|
-
} catch (error) {
|
|
251
|
-
return { success: false, error: error.message };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
async unretweet(tweetId) {
|
|
255
|
-
try {
|
|
256
|
-
const page = await this.ensureBrowser();
|
|
257
|
-
await page.goto(`${X_BASE}/i/status/${tweetId}`, {
|
|
258
|
-
waitUntil: "domcontentloaded"
|
|
259
|
-
});
|
|
260
|
-
await page.waitForTimeout(2e3);
|
|
261
|
-
await page.locator('[data-testid="unretweet"]').first().click();
|
|
262
|
-
await page.waitForTimeout(1e3);
|
|
263
|
-
await page.locator('[data-testid="unretweetConfirm"]').click();
|
|
264
|
-
await page.waitForTimeout(1e3);
|
|
265
|
-
await this.saveSession();
|
|
266
|
-
return { success: true, tweetId };
|
|
267
|
-
} catch (error) {
|
|
268
|
-
return { success: false, error: error.message };
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
async followUser(userId) {
|
|
272
|
-
try {
|
|
273
|
-
const page = await this.ensureBrowser();
|
|
274
|
-
await page.goto(`${X_BASE}/${userId}`, { waitUntil: "domcontentloaded" });
|
|
275
|
-
await page.waitForTimeout(2e3);
|
|
276
|
-
const followButton = page.locator('[data-testid$="-follow"]').first();
|
|
277
|
-
await followButton.click();
|
|
278
|
-
await page.waitForTimeout(1e3);
|
|
279
|
-
logInteraction({
|
|
280
|
-
id: `int-${Date.now()}`,
|
|
281
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
282
|
-
type: "follow",
|
|
283
|
-
targetUserId: userId,
|
|
284
|
-
creditsUsed: 0,
|
|
285
|
-
success: true
|
|
286
|
-
});
|
|
287
|
-
await this.saveSession();
|
|
288
|
-
return { success: true };
|
|
289
|
-
} catch (error) {
|
|
290
|
-
return { success: false, error: error.message };
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
async unfollowUser(userId) {
|
|
294
|
-
try {
|
|
295
|
-
const page = await this.ensureBrowser();
|
|
296
|
-
await page.goto(`${X_BASE}/${userId}`, { waitUntil: "domcontentloaded" });
|
|
297
|
-
await page.waitForTimeout(2e3);
|
|
298
|
-
const unfollowButton = page.locator('[data-testid$="-unfollow"]').first();
|
|
299
|
-
await unfollowButton.click();
|
|
300
|
-
await page.waitForTimeout(1e3);
|
|
301
|
-
await page.locator('[data-testid="confirmationSheetConfirm"]').click();
|
|
302
|
-
await page.waitForTimeout(1e3);
|
|
303
|
-
await this.saveSession();
|
|
304
|
-
return { success: true };
|
|
305
|
-
} catch (error) {
|
|
306
|
-
return { success: false, error: error.message };
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
async getTimeline(options) {
|
|
310
|
-
try {
|
|
311
|
-
const page = await this.ensureBrowser();
|
|
312
|
-
await page.goto(X_BASE, { waitUntil: "domcontentloaded" });
|
|
313
|
-
await page.waitForTimeout(3e3);
|
|
314
|
-
const tweets = await this.scrapeTweets(page, options?.count ?? 20);
|
|
315
|
-
return tweets;
|
|
316
|
-
} catch (error) {
|
|
317
|
-
logger.error("Failed to read timeline via browser", error);
|
|
318
|
-
return [];
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
async getMentions(options) {
|
|
322
|
-
try {
|
|
323
|
-
const page = await this.ensureBrowser();
|
|
324
|
-
await page.goto(`${X_BASE}/notifications/mentions`, {
|
|
325
|
-
waitUntil: "domcontentloaded"
|
|
326
|
-
});
|
|
327
|
-
await page.waitForTimeout(3e3);
|
|
328
|
-
const tweets = await this.scrapeTweets(page, options?.count ?? 20);
|
|
329
|
-
return tweets;
|
|
330
|
-
} catch (error) {
|
|
331
|
-
logger.error("Failed to read mentions via browser", error);
|
|
332
|
-
return [];
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
async searchTweets(query, options) {
|
|
336
|
-
try {
|
|
337
|
-
const page = await this.ensureBrowser();
|
|
338
|
-
const encodedQuery = encodeURIComponent(query);
|
|
339
|
-
await page.goto(`${X_BASE}/search?q=${encodedQuery}&f=live`, {
|
|
340
|
-
waitUntil: "domcontentloaded"
|
|
341
|
-
});
|
|
342
|
-
await page.waitForTimeout(3e3);
|
|
343
|
-
const tweets = await this.scrapeTweets(page, options?.count ?? 20);
|
|
344
|
-
return tweets;
|
|
345
|
-
} catch (error) {
|
|
346
|
-
logger.error("Failed to search via browser", error);
|
|
347
|
-
return [];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
async getTweet(_tweetId) {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
async getProfile(handle) {
|
|
354
|
-
const page = await this.ensureBrowser();
|
|
355
|
-
await page.goto(`${X_BASE}/${handle}`, { waitUntil: "domcontentloaded" });
|
|
356
|
-
await page.waitForTimeout(3e3);
|
|
357
|
-
const name = await page.locator('[data-testid="UserName"] span').first().textContent() ?? handle;
|
|
358
|
-
const bio = await page.locator('[data-testid="UserDescription"]').textContent().catch(() => "") ?? "";
|
|
359
|
-
const followersText = await page.locator(`a[href="/${handle}/verified_followers"] span`).first().textContent().catch(() => "0") ?? "0";
|
|
360
|
-
const followingText = await page.locator(`a[href="/${handle}/following"] span`).first().textContent().catch(() => "0") ?? "0";
|
|
361
|
-
await this.saveSession();
|
|
362
|
-
return {
|
|
363
|
-
id: handle,
|
|
364
|
-
handle,
|
|
365
|
-
name,
|
|
366
|
-
bio,
|
|
367
|
-
followersCount: parseInt(followersText.replace(/[,K.M]/g, "")) || 0,
|
|
368
|
-
followingCount: parseInt(followingText.replace(/[,K.M]/g, "")) || 0,
|
|
369
|
-
tweetCount: 0,
|
|
370
|
-
verified: false
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
async scrapeTweets(page, count) {
|
|
374
|
-
const tweets = [];
|
|
375
|
-
const articles = page.locator('article[data-testid="tweet"]');
|
|
376
|
-
const articleCount = await articles.count();
|
|
377
|
-
for (let i = 0; i < Math.min(articleCount, count); i++) {
|
|
378
|
-
try {
|
|
379
|
-
const article = articles.nth(i);
|
|
380
|
-
const tweetText = await article.locator('[data-testid="tweetText"]').textContent().catch(() => "") ?? "";
|
|
381
|
-
const userLink = article.locator('a[role="link"][href*="/"]').first();
|
|
382
|
-
const href = await userLink.getAttribute("href").catch(() => "") ?? "";
|
|
383
|
-
const authorHandle = href.replace("/", "");
|
|
384
|
-
const time = article.locator("time").first();
|
|
385
|
-
const datetime = await time.getAttribute("datetime").catch(() => "") ?? "";
|
|
386
|
-
tweets.push({
|
|
387
|
-
id: `browser-${Date.now()}-${i}`,
|
|
388
|
-
text: tweetText,
|
|
389
|
-
authorId: authorHandle,
|
|
390
|
-
authorHandle,
|
|
391
|
-
createdAt: datetime
|
|
392
|
-
});
|
|
393
|
-
} catch {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return tweets;
|
|
398
|
-
}
|
|
399
|
-
async close() {
|
|
400
|
-
if (this.page) await this.page.close().catch(() => {
|
|
401
|
-
});
|
|
402
|
-
if (this.context) await this.context.close().catch(() => {
|
|
403
|
-
});
|
|
404
|
-
if (this.browser) await this.browser.close().catch(() => {
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
export {
|
|
409
|
-
XBrowserClient
|
|
410
|
-
};
|
|
411
|
-
//# sourceMappingURL=client-NVI3ZD4G.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/x-client/browser/client.ts"],"sourcesContent":["import { chromium, type Browser, type Page, type BrowserContext } from \"playwright\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { loadCredentials } from \"../../utils/crypto.js\";\nimport { loadIdentity, identityExists } from \"../../identity/index.js\";\nimport { logInteraction } from \"../../memory/index.js\";\nimport { rateLimiter } from \"../rate-limiter.js\";\nimport { paths, ensureDirectories } from \"../../utils/paths.js\";\nimport { logger } from \"../../utils/logger.js\";\nimport type {\n XClientInterface,\n Tweet,\n UserProfile,\n PostResult,\n TimelineOptions,\n SearchOptions,\n} from \"../types.js\";\n\nconst X_BASE = \"https://x.com\";\n\nexport class XBrowserClient implements XClientInterface {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n\n private async ensureBrowser(): Promise<Page> {\n if (this.page && !this.page.isClosed()) return this.page;\n\n this.browser = await chromium.launch({ headless: true });\n\n // Restore session if available\n if (existsSync(paths.browserAuth)) {\n const storageState = JSON.parse(readFileSync(paths.browserAuth, \"utf-8\"));\n this.context = await this.browser.newContext({ storageState });\n } else {\n this.context = await this.browser.newContext();\n }\n\n this.page = await this.context.newPage();\n\n // Check if we're logged in\n await this.page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n const isLoggedIn = await this.page\n .locator('[data-testid=\"SideNav_AccountSwitcher_Button\"]')\n .isVisible({ timeout: 5000 })\n .catch(() => false);\n\n if (!isLoggedIn) {\n await this.login();\n }\n\n return this.page;\n }\n\n private async login(): Promise<void> {\n const creds = loadCredentials();\n if (!creds.username || !creds.password) {\n throw new Error(\"Browser mode requires username and password credentials\");\n }\n\n const page = this.page!;\n\n await page.goto(`${X_BASE}/login`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n // Enter username\n const usernameInput = page.locator('input[autocomplete=\"username\"]');\n await usernameInput.waitFor({ timeout: 10000 });\n await usernameInput.fill(creds.username);\n await page.locator('text=Next').click();\n await page.waitForTimeout(2000);\n\n // Check for email verification step\n const emailInput = page.locator('input[data-testid=\"ocfEnterTextTextInput\"]');\n const emailVisible = await emailInput.isVisible({ timeout: 3000 }).catch(() => false);\n if (emailVisible && creds.email) {\n await emailInput.fill(creds.email);\n await page.locator('text=Next').click();\n await page.waitForTimeout(2000);\n }\n\n // Enter password\n const passwordInput = page.locator('input[type=\"password\"]');\n await passwordInput.waitFor({ timeout: 10000 });\n await passwordInput.fill(creds.password);\n await page.locator('[data-testid=\"LoginForm_Login_Button\"]').click();\n await page.waitForTimeout(3000);\n\n // Save session\n await this.saveSession();\n logger.info(\"Logged into X via browser\");\n }\n\n private async saveSession(): Promise<void> {\n if (!this.context) return;\n ensureDirectories();\n const state = await this.context.storageState();\n writeFileSync(paths.browserAuth, JSON.stringify(state));\n }\n\n private getHandle(): string {\n if (identityExists()) {\n return loadIdentity().handle;\n }\n const creds = loadCredentials();\n if (creds.username) return creds.username;\n throw new Error(\"No handle found. Create a Spore identity first.\");\n }\n\n private async waitForNavigation(page: Page, timeout = 5000): Promise<void> {\n await page.waitForTimeout(timeout);\n }\n\n async postTweet(content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const page = await this.ensureBrowser();\n await page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n // Click the tweet compose area\n const composeButton = page.locator('[data-testid=\"tweetButtonInline\"]').first();\n const composeArea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n\n // Try clicking the compose area directly\n const areaVisible = await composeArea.isVisible({ timeout: 3000 }).catch(() => false);\n if (!areaVisible) {\n // Click the compose button in sidebar\n await page.locator('[data-testid=\"SideNav_NewTweet_Button\"]').click();\n await page.waitForTimeout(1000);\n }\n\n // Type the tweet\n const textarea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n await textarea.waitFor({ timeout: 5000 });\n await textarea.fill(content);\n await page.waitForTimeout(500);\n\n // Click the post button\n await page.locator('[data-testid=\"tweetButton\"]').click();\n await page.waitForTimeout(3000);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"post\",\n content,\n creditsUsed: 1,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n logger.error(\"Failed to post tweet via browser\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async postTweetWithMedia(content: string, _mediaBuffer: Buffer): Promise<PostResult> {\n // Browser client doesn't support media upload — fall back to text-only post\n logger.warn(\"Browser client does not support media upload, posting without image\");\n return this.postTweet(content);\n }\n\n async replyToTweetWithMedia(tweetId: string, content: string, _mediaBuffer: Buffer): Promise<PostResult> {\n logger.warn(\"Browser client does not support media upload, replying without image\");\n return this.replyToTweet(tweetId, content);\n }\n\n async replyToTweet(tweetId: string, content: string): Promise<PostResult> {\n if (!rateLimiter.canPost()) {\n return { success: false, error: \"Monthly post limit reached\" };\n }\n\n try {\n const page = await this.ensureBrowser();\n\n // Navigate to the tweet using /i/status/ which works for any author's tweet\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n // Click reply area\n const replyArea = page.locator('[data-testid=\"tweetTextarea_0\"]').first();\n await replyArea.waitFor({ timeout: 5000 });\n await replyArea.fill(content);\n await page.waitForTimeout(500);\n\n // Click reply button\n await page.locator('[data-testid=\"tweetButton\"]').click();\n await page.waitForTimeout(3000);\n\n rateLimiter.consume();\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"reply\",\n inReplyTo: tweetId,\n content,\n creditsUsed: 1,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n logger.error(\"Failed to reply via browser\", error);\n return { success: false, error: (error as Error).message };\n }\n }\n\n async deleteTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n const handle = this.getHandle();\n\n await page.goto(`${X_BASE}/${handle}/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n // Click the more options menu\n await page.locator('[data-testid=\"caret\"]').first().click();\n await page.waitForTimeout(1000);\n\n // Click delete\n await page.locator('text=Delete').click();\n await page.waitForTimeout(1000);\n\n // Confirm\n await page.locator('[data-testid=\"confirmationSheetConfirm\"]').click();\n await page.waitForTimeout(2000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async likeTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n\n // Navigate to a page that shows the tweet, then like it\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"like\"]').first().click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"like\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unlikeTweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"unlike\"]').first().click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async retweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"retweet\"]').first().click();\n await page.waitForTimeout(1000);\n await page.locator('[data-testid=\"retweetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"retweet\",\n tweetId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unretweet(tweetId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/i/status/${tweetId}`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(2000);\n\n await page.locator('[data-testid=\"unretweet\"]').first().click();\n await page.waitForTimeout(1000);\n await page.locator('[data-testid=\"unretweetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true, tweetId };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async followUser(userId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n // userId here may be a handle in browser mode\n await page.goto(`${X_BASE}/${userId}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n const followButton = page.locator('[data-testid$=\"-follow\"]').first();\n await followButton.click();\n await page.waitForTimeout(1000);\n\n logInteraction({\n id: `int-${Date.now()}`,\n timestamp: new Date().toISOString(),\n type: \"follow\",\n targetUserId: userId,\n creditsUsed: 0,\n success: true,\n });\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async unfollowUser(userId: string): Promise<PostResult> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/${userId}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(2000);\n\n const unfollowButton = page.locator('[data-testid$=\"-unfollow\"]').first();\n await unfollowButton.click();\n await page.waitForTimeout(1000);\n\n // Confirm unfollow\n await page.locator('[data-testid=\"confirmationSheetConfirm\"]').click();\n await page.waitForTimeout(1000);\n\n await this.saveSession();\n return { success: true };\n } catch (error) {\n return { success: false, error: (error as Error).message };\n }\n }\n\n async getTimeline(options?: TimelineOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(X_BASE, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to read timeline via browser\", error);\n return [];\n }\n }\n\n async getMentions(options?: TimelineOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/notifications/mentions`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to read mentions via browser\", error);\n return [];\n }\n }\n\n async searchTweets(query: string, options?: SearchOptions): Promise<Tweet[]> {\n try {\n const page = await this.ensureBrowser();\n const encodedQuery = encodeURIComponent(query);\n await page.goto(`${X_BASE}/search?q=${encodedQuery}&f=live`, {\n waitUntil: \"domcontentloaded\",\n });\n await page.waitForTimeout(3000);\n\n const tweets = await this.scrapeTweets(page, options?.count ?? 20);\n return tweets;\n } catch (error) {\n logger.error(\"Failed to search via browser\", error);\n return [];\n }\n }\n\n async getTweet(_tweetId: string): Promise<Tweet | null> {\n // Browser client can't reliably look up individual tweets — degrade gracefully\n return null;\n }\n\n async getProfile(handle: string): Promise<UserProfile> {\n const page = await this.ensureBrowser();\n await page.goto(`${X_BASE}/${handle}`, { waitUntil: \"domcontentloaded\" });\n await page.waitForTimeout(3000);\n\n const name =\n (await page\n .locator('[data-testid=\"UserName\"] span')\n .first()\n .textContent()) ?? handle;\n\n const bio =\n (await page\n .locator('[data-testid=\"UserDescription\"]')\n .textContent()\n .catch(() => \"\")) ?? \"\";\n\n // Parse follower/following counts from the profile page\n const followersText =\n (await page\n .locator(`a[href=\"/${handle}/verified_followers\"] span`)\n .first()\n .textContent()\n .catch(() => \"0\")) ?? \"0\";\n\n const followingText =\n (await page\n .locator(`a[href=\"/${handle}/following\"] span`)\n .first()\n .textContent()\n .catch(() => \"0\")) ?? \"0\";\n\n await this.saveSession();\n\n return {\n id: handle,\n handle,\n name,\n bio,\n followersCount: parseInt(followersText.replace(/[,K.M]/g, \"\")) || 0,\n followingCount: parseInt(followingText.replace(/[,K.M]/g, \"\")) || 0,\n tweetCount: 0,\n verified: false,\n };\n }\n\n private async scrapeTweets(page: Page, count: number): Promise<Tweet[]> {\n const tweets: Tweet[] = [];\n\n const articles = page.locator('article[data-testid=\"tweet\"]');\n const articleCount = await articles.count();\n\n for (let i = 0; i < Math.min(articleCount, count); i++) {\n try {\n const article = articles.nth(i);\n\n const tweetText =\n (await article\n .locator('[data-testid=\"tweetText\"]')\n .textContent()\n .catch(() => \"\")) ?? \"\";\n\n const userLink = article\n .locator('a[role=\"link\"][href*=\"/\"]')\n .first();\n const href = (await userLink.getAttribute(\"href\").catch(() => \"\")) ?? \"\";\n const authorHandle = href.replace(\"/\", \"\");\n\n const time = article.locator(\"time\").first();\n const datetime = (await time.getAttribute(\"datetime\").catch(() => \"\")) ?? \"\";\n\n tweets.push({\n id: `browser-${Date.now()}-${i}`,\n text: tweetText,\n authorId: authorHandle,\n authorHandle,\n createdAt: datetime,\n });\n } catch {\n continue;\n }\n }\n\n return tweets;\n }\n\n async close(): Promise<void> {\n if (this.page) await this.page.close().catch(() => {});\n if (this.context) await this.context.close().catch(() => {});\n if (this.browser) await this.browser.close().catch(() => {});\n }\n}\n"],"mappings":"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","names":[]}
|