scrapebadger 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -0
- package/dist/{index-DnmHcsYR.d.cts → index-BbCkdZXy.d.cts} +959 -6
- package/dist/{index-DnmHcsYR.d.ts → index-BbCkdZXy.d.ts} +959 -6
- package/dist/index.d.cts +4 -85
- package/dist/index.d.ts +4 -85
- package/dist/index.js +702 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +695 -10
- package/dist/index.mjs.map +1 -1
- package/dist/twitter/index.d.cts +2 -1
- package/dist/twitter/index.d.ts +2 -1
- package/dist/twitter/index.js +697 -5
- package/dist/twitter/index.js.map +1 -1
- package/dist/twitter/index.mjs +692 -6
- package/dist/twitter/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var events = require('events');
|
|
5
|
+
var WebSocket = require('ws');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
|
|
10
|
+
|
|
3
11
|
// src/internal/exceptions.ts
|
|
4
12
|
var ScrapeBadgerError = class _ScrapeBadgerError extends Error {
|
|
5
13
|
constructor(message) {
|
|
@@ -94,6 +102,23 @@ var AccountRestrictedError = class _AccountRestrictedError extends ScrapeBadgerE
|
|
|
94
102
|
Object.setPrototypeOf(this, _AccountRestrictedError.prototype);
|
|
95
103
|
}
|
|
96
104
|
};
|
|
105
|
+
var ConflictError = class _ConflictError extends ScrapeBadgerError {
|
|
106
|
+
constructor(message = "Resource conflict.") {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = "ConflictError";
|
|
109
|
+
Object.setPrototypeOf(this, _ConflictError.prototype);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var WebSocketStreamError = class _WebSocketStreamError extends ScrapeBadgerError {
|
|
113
|
+
/** WebSocket close code or server error code */
|
|
114
|
+
code;
|
|
115
|
+
constructor(message = "WebSocket stream error", code) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = "WebSocketStreamError";
|
|
118
|
+
this.code = code;
|
|
119
|
+
Object.setPrototypeOf(this, _WebSocketStreamError.prototype);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
97
122
|
|
|
98
123
|
// src/internal/client.ts
|
|
99
124
|
var BaseClient = class {
|
|
@@ -142,9 +167,7 @@ var BaseClient = class {
|
|
|
142
167
|
} catch (error) {
|
|
143
168
|
lastError = error;
|
|
144
169
|
if (error instanceof ScrapeBadgerError && !(error instanceof RateLimitError)) {
|
|
145
|
-
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
170
|
+
throw error;
|
|
148
171
|
}
|
|
149
172
|
if (attempt === this.config.maxRetries) {
|
|
150
173
|
break;
|
|
@@ -176,7 +199,10 @@ var BaseClient = class {
|
|
|
176
199
|
return response;
|
|
177
200
|
} catch (error) {
|
|
178
201
|
if (error instanceof Error && error.name === "AbortError") {
|
|
179
|
-
throw new TimeoutError(
|
|
202
|
+
throw new TimeoutError(
|
|
203
|
+
`Request timed out after ${this.config.timeout}ms`,
|
|
204
|
+
this.config.timeout
|
|
205
|
+
);
|
|
180
206
|
}
|
|
181
207
|
throw error;
|
|
182
208
|
} finally {
|
|
@@ -212,6 +238,8 @@ var BaseClient = class {
|
|
|
212
238
|
throw new AuthenticationError(message);
|
|
213
239
|
case 404:
|
|
214
240
|
throw new NotFoundError(message);
|
|
241
|
+
case 409:
|
|
242
|
+
throw new ConflictError(message);
|
|
215
243
|
case 422:
|
|
216
244
|
throw new ValidationError(message, errorData.errors);
|
|
217
245
|
case 429:
|
|
@@ -519,6 +547,7 @@ var TweetsClient = class {
|
|
|
519
547
|
params: {
|
|
520
548
|
query,
|
|
521
549
|
query_type: options.queryType ?? "Top",
|
|
550
|
+
count: options.count,
|
|
522
551
|
cursor: options.cursor
|
|
523
552
|
}
|
|
524
553
|
}
|
|
@@ -1077,11 +1106,7 @@ var CommunitiesClient = class {
|
|
|
1077
1106
|
name: item.name ?? "",
|
|
1078
1107
|
profile_image_url: item.profile_image_url,
|
|
1079
1108
|
verified: item.verified ?? false,
|
|
1080
|
-
is_blue_verified: item.is_blue_verified
|
|
1081
|
-
followers_count: 0,
|
|
1082
|
-
following_count: 0,
|
|
1083
|
-
tweet_count: 0,
|
|
1084
|
-
listed_count: 0
|
|
1109
|
+
is_blue_verified: item.is_blue_verified
|
|
1085
1110
|
},
|
|
1086
1111
|
role: item.role,
|
|
1087
1112
|
joined_at: item.joined_at
|
|
@@ -1409,6 +1434,667 @@ var GeoClient = class {
|
|
|
1409
1434
|
return createPaginatedResponse(response.data ?? []);
|
|
1410
1435
|
}
|
|
1411
1436
|
};
|
|
1437
|
+
var MIN_RECONNECT_DELAY_SECONDS = 5;
|
|
1438
|
+
function wsUrlFromBase(baseUrl) {
|
|
1439
|
+
if (baseUrl.startsWith("https://")) {
|
|
1440
|
+
return baseUrl.replace("https://", "wss://") + "/v1/twitter/stream";
|
|
1441
|
+
}
|
|
1442
|
+
if (baseUrl.startsWith("http://")) {
|
|
1443
|
+
return baseUrl.replace("http://", "ws://") + "/v1/twitter/stream";
|
|
1444
|
+
}
|
|
1445
|
+
return baseUrl + "/v1/twitter/stream";
|
|
1446
|
+
}
|
|
1447
|
+
function parseEvent(raw) {
|
|
1448
|
+
const type = raw["type"];
|
|
1449
|
+
switch (type) {
|
|
1450
|
+
case "connected":
|
|
1451
|
+
return {
|
|
1452
|
+
type: "connected",
|
|
1453
|
+
connectionId: raw["connection_id"],
|
|
1454
|
+
apiKeyId: raw["api_key_id"]
|
|
1455
|
+
};
|
|
1456
|
+
case "ping":
|
|
1457
|
+
return {
|
|
1458
|
+
type: "ping",
|
|
1459
|
+
timestamp: raw["timestamp"]
|
|
1460
|
+
};
|
|
1461
|
+
case "tweet":
|
|
1462
|
+
return {
|
|
1463
|
+
type: "tweet",
|
|
1464
|
+
monitorId: raw["monitor_id"],
|
|
1465
|
+
tweetId: raw["tweet_id"],
|
|
1466
|
+
authorUsername: raw["author_username"],
|
|
1467
|
+
tweetPublishedAt: raw["tweet_published_at"],
|
|
1468
|
+
detectedAt: raw["detected_at"],
|
|
1469
|
+
latencyMs: raw["latency_ms"],
|
|
1470
|
+
tweet: raw["tweet"]
|
|
1471
|
+
};
|
|
1472
|
+
case "error":
|
|
1473
|
+
return {
|
|
1474
|
+
type: "error",
|
|
1475
|
+
code: raw["code"],
|
|
1476
|
+
message: raw["message"]
|
|
1477
|
+
};
|
|
1478
|
+
default:
|
|
1479
|
+
return {
|
|
1480
|
+
type: "error",
|
|
1481
|
+
code: 0,
|
|
1482
|
+
message: `Unknown event type: ${String(type)}`
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
var StreamClient = class {
|
|
1487
|
+
client;
|
|
1488
|
+
constructor(client) {
|
|
1489
|
+
this.client = client;
|
|
1490
|
+
}
|
|
1491
|
+
// ===========================================================================
|
|
1492
|
+
// Monitor CRUD
|
|
1493
|
+
// ===========================================================================
|
|
1494
|
+
/**
|
|
1495
|
+
* Create a new stream monitor.
|
|
1496
|
+
*
|
|
1497
|
+
* @param params - Monitor configuration.
|
|
1498
|
+
* @returns The created StreamMonitor.
|
|
1499
|
+
* @throws InsufficientCreditsError - Credit balance below tier threshold (402).
|
|
1500
|
+
* @throws ValidationError - Invalid username or interval (422).
|
|
1501
|
+
* @throws ScrapeBadgerError - Name conflict (409).
|
|
1502
|
+
* @throws AuthenticationError - Invalid API key (401).
|
|
1503
|
+
*
|
|
1504
|
+
* @example
|
|
1505
|
+
* ```typescript
|
|
1506
|
+
* const monitor = await client.twitter.stream.createMonitor({
|
|
1507
|
+
* name: "Breaking News",
|
|
1508
|
+
* usernames: ["cnnbrk", "bbcbreaking"],
|
|
1509
|
+
* pollIntervalSeconds: 5,
|
|
1510
|
+
* });
|
|
1511
|
+
* ```
|
|
1512
|
+
*/
|
|
1513
|
+
async createMonitor(params) {
|
|
1514
|
+
const body = {
|
|
1515
|
+
name: params.name,
|
|
1516
|
+
usernames: params.usernames,
|
|
1517
|
+
poll_interval_seconds: params.pollIntervalSeconds
|
|
1518
|
+
};
|
|
1519
|
+
if (params.webhookUrl !== void 0) body["webhook_url"] = params.webhookUrl;
|
|
1520
|
+
if (params.webhookSecret !== void 0) body["webhook_secret"] = params.webhookSecret;
|
|
1521
|
+
return this.client.request("/v1/twitter/stream/monitors", {
|
|
1522
|
+
method: "POST",
|
|
1523
|
+
body
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* List stream monitors for the authenticated API key.
|
|
1528
|
+
*
|
|
1529
|
+
* @param options - Filter and pagination options.
|
|
1530
|
+
* @returns StreamMonitorList with pagination metadata.
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* ```typescript
|
|
1534
|
+
* const { monitors, total } = await client.twitter.stream.listMonitors({
|
|
1535
|
+
* status: "active",
|
|
1536
|
+
* });
|
|
1537
|
+
* console.log(`${total} active monitors`);
|
|
1538
|
+
* ```
|
|
1539
|
+
*/
|
|
1540
|
+
async listMonitors(options) {
|
|
1541
|
+
const params = {
|
|
1542
|
+
page: options?.page ?? 1,
|
|
1543
|
+
page_size: options?.pageSize ?? 20,
|
|
1544
|
+
status: options?.status
|
|
1545
|
+
};
|
|
1546
|
+
return this.client.request("/v1/twitter/stream/monitors", {
|
|
1547
|
+
params
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Get a single stream monitor by ID.
|
|
1552
|
+
*
|
|
1553
|
+
* @param monitorId - UUID of the monitor.
|
|
1554
|
+
* @returns The StreamMonitor.
|
|
1555
|
+
* @throws NotFoundError - No monitor with that ID for this API key.
|
|
1556
|
+
*
|
|
1557
|
+
* @example
|
|
1558
|
+
* ```typescript
|
|
1559
|
+
* const monitor = await client.twitter.stream.getMonitor("550e8400-...");
|
|
1560
|
+
* console.log(`${monitor.name}: ${monitor.status}`);
|
|
1561
|
+
* ```
|
|
1562
|
+
*/
|
|
1563
|
+
async getMonitor(monitorId) {
|
|
1564
|
+
return this.client.request(`/v1/twitter/stream/monitors/${monitorId}`);
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Partially update a stream monitor.
|
|
1568
|
+
*
|
|
1569
|
+
* Only fields that are explicitly set in params are sent to the server.
|
|
1570
|
+
*
|
|
1571
|
+
* @param monitorId - UUID of the monitor.
|
|
1572
|
+
* @param params - Fields to update (all optional).
|
|
1573
|
+
* @returns The updated StreamMonitor.
|
|
1574
|
+
* @throws NotFoundError - Monitor not found for this API key.
|
|
1575
|
+
* @throws InsufficientCreditsError - When resuming with insufficient credits.
|
|
1576
|
+
*
|
|
1577
|
+
* @example
|
|
1578
|
+
* ```typescript
|
|
1579
|
+
* const monitor = await client.twitter.stream.updateMonitor("550e8400-...", {
|
|
1580
|
+
* pollIntervalSeconds: 60,
|
|
1581
|
+
* });
|
|
1582
|
+
* ```
|
|
1583
|
+
*/
|
|
1584
|
+
async updateMonitor(monitorId, params) {
|
|
1585
|
+
const body = {};
|
|
1586
|
+
if (params.name !== void 0) body["name"] = params.name;
|
|
1587
|
+
if (params.usernames !== void 0) body["usernames"] = params.usernames;
|
|
1588
|
+
if (params.pollIntervalSeconds !== void 0)
|
|
1589
|
+
body["poll_interval_seconds"] = params.pollIntervalSeconds;
|
|
1590
|
+
if (params.status !== void 0) body["status"] = params.status;
|
|
1591
|
+
if (params.webhookUrl !== void 0) body["webhook_url"] = params.webhookUrl;
|
|
1592
|
+
if (params.webhookSecret !== void 0) body["webhook_secret"] = params.webhookSecret;
|
|
1593
|
+
return this.client.request(`/v1/twitter/stream/monitors/${monitorId}`, {
|
|
1594
|
+
method: "PATCH",
|
|
1595
|
+
body
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Pause an active stream monitor.
|
|
1600
|
+
*
|
|
1601
|
+
* Convenience wrapper around updateMonitor({ status: "paused" }).
|
|
1602
|
+
*
|
|
1603
|
+
* @param monitorId - UUID of the monitor.
|
|
1604
|
+
* @returns The updated StreamMonitor with status="paused".
|
|
1605
|
+
*/
|
|
1606
|
+
async pauseMonitor(monitorId) {
|
|
1607
|
+
return this.updateMonitor(monitorId, { status: "paused" });
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Resume a paused stream monitor.
|
|
1611
|
+
*
|
|
1612
|
+
* Convenience wrapper around updateMonitor({ status: "active" }).
|
|
1613
|
+
*
|
|
1614
|
+
* @param monitorId - UUID of the monitor.
|
|
1615
|
+
* @returns The updated StreamMonitor with status="active".
|
|
1616
|
+
* @throws InsufficientCreditsError - If credits are below the tier threshold.
|
|
1617
|
+
*/
|
|
1618
|
+
async resumeMonitor(monitorId) {
|
|
1619
|
+
return this.updateMonitor(monitorId, { status: "active" });
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Delete a stream monitor and all its associated logs. Irreversible.
|
|
1623
|
+
*
|
|
1624
|
+
* @param monitorId - UUID of the monitor.
|
|
1625
|
+
* @throws NotFoundError - Monitor not found for this API key.
|
|
1626
|
+
*
|
|
1627
|
+
* @example
|
|
1628
|
+
* ```typescript
|
|
1629
|
+
* await client.twitter.stream.deleteMonitor("550e8400-...");
|
|
1630
|
+
* ```
|
|
1631
|
+
*/
|
|
1632
|
+
async deleteMonitor(monitorId) {
|
|
1633
|
+
await this.client.request(`/v1/twitter/stream/monitors/${monitorId}`, {
|
|
1634
|
+
method: "DELETE"
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
// ===========================================================================
|
|
1638
|
+
// Delivery and Billing Logs
|
|
1639
|
+
// ===========================================================================
|
|
1640
|
+
/**
|
|
1641
|
+
* List tweet delivery logs.
|
|
1642
|
+
*
|
|
1643
|
+
* @param options - Filter and pagination options.
|
|
1644
|
+
* @returns DeliveryLogList with pagination metadata.
|
|
1645
|
+
*/
|
|
1646
|
+
async listDeliveryLogs(options) {
|
|
1647
|
+
const params = {
|
|
1648
|
+
page: options?.page ?? 1,
|
|
1649
|
+
page_size: options?.pageSize ?? 20,
|
|
1650
|
+
sort: options?.sort ?? "desc",
|
|
1651
|
+
monitor_id: options?.monitorId,
|
|
1652
|
+
author_username: options?.authorUsername,
|
|
1653
|
+
delivery_status: options?.deliveryStatus
|
|
1654
|
+
};
|
|
1655
|
+
return this.client.request("/v1/twitter/stream/logs", { params });
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* List billing activity logs.
|
|
1659
|
+
*
|
|
1660
|
+
* @param options - Filter and pagination options.
|
|
1661
|
+
* @returns BillingLogList with pagination metadata.
|
|
1662
|
+
*/
|
|
1663
|
+
async listBillingLogs(options) {
|
|
1664
|
+
const params = {
|
|
1665
|
+
page: options?.page ?? 1,
|
|
1666
|
+
page_size: options?.pageSize ?? 20,
|
|
1667
|
+
monitor_id: options?.monitorId
|
|
1668
|
+
};
|
|
1669
|
+
return this.client.request("/v1/twitter/stream/billing-logs", { params });
|
|
1670
|
+
}
|
|
1671
|
+
// ===========================================================================
|
|
1672
|
+
// Filter Rules CRUD
|
|
1673
|
+
// ===========================================================================
|
|
1674
|
+
/**
|
|
1675
|
+
* Create a new tweet filter rule.
|
|
1676
|
+
*
|
|
1677
|
+
* @param params - Filter rule configuration.
|
|
1678
|
+
* @returns The created FilterRuleResponse.
|
|
1679
|
+
* @throws ValidationError - Invalid query or interval (422).
|
|
1680
|
+
* @throws InsufficientCreditsError - Credit balance below tier threshold (402).
|
|
1681
|
+
* @throws AuthenticationError - Invalid API key (401).
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* ```typescript
|
|
1685
|
+
* const rule = await client.twitter.stream.createFilterRule({
|
|
1686
|
+
* tag: "python news",
|
|
1687
|
+
* query: "#python lang:en -is:retweet",
|
|
1688
|
+
* interval_seconds: 60,
|
|
1689
|
+
* });
|
|
1690
|
+
* console.log(`Created: ${rule.id}, tier: ${rule.pricing_tier}`);
|
|
1691
|
+
* ```
|
|
1692
|
+
*/
|
|
1693
|
+
async createFilterRule(params) {
|
|
1694
|
+
const body = {
|
|
1695
|
+
tag: params.tag,
|
|
1696
|
+
query: params.query,
|
|
1697
|
+
interval_seconds: params.interval_seconds
|
|
1698
|
+
};
|
|
1699
|
+
if (params.webhook_url !== void 0) body["webhook_url"] = params.webhook_url;
|
|
1700
|
+
if (params.webhook_secret !== void 0) body["webhook_secret"] = params.webhook_secret;
|
|
1701
|
+
if (params.max_results_per_poll !== void 0)
|
|
1702
|
+
body["max_results_per_poll"] = params.max_results_per_poll;
|
|
1703
|
+
return this.client.request("/v1/twitter/stream/filter-rules", {
|
|
1704
|
+
method: "POST",
|
|
1705
|
+
body
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* List filter rules for the authenticated API key.
|
|
1710
|
+
*
|
|
1711
|
+
* @param options - Filter and pagination options.
|
|
1712
|
+
* @returns FilterRuleListResponse with pagination metadata.
|
|
1713
|
+
*
|
|
1714
|
+
* @example
|
|
1715
|
+
* ```typescript
|
|
1716
|
+
* const { rules, total } = await client.twitter.stream.listFilterRules({
|
|
1717
|
+
* status: "active",
|
|
1718
|
+
* });
|
|
1719
|
+
* console.log(`${total} active rules`);
|
|
1720
|
+
* ```
|
|
1721
|
+
*/
|
|
1722
|
+
async listFilterRules(options) {
|
|
1723
|
+
const params = {
|
|
1724
|
+
limit: options?.limit ?? 20,
|
|
1725
|
+
offset: options?.offset ?? 0,
|
|
1726
|
+
status: options?.status
|
|
1727
|
+
};
|
|
1728
|
+
return this.client.request("/v1/twitter/stream/filter-rules", {
|
|
1729
|
+
params
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Get a single filter rule by ID.
|
|
1734
|
+
*
|
|
1735
|
+
* @param ruleId - UUID of the filter rule.
|
|
1736
|
+
* @returns The FilterRuleResponse.
|
|
1737
|
+
* @throws NotFoundError - No rule with that ID for this API key.
|
|
1738
|
+
*
|
|
1739
|
+
* @example
|
|
1740
|
+
* ```typescript
|
|
1741
|
+
* const rule = await client.twitter.stream.getFilterRule("550e8400-...");
|
|
1742
|
+
* console.log(`${rule.tag}: ${rule.status}`);
|
|
1743
|
+
* ```
|
|
1744
|
+
*/
|
|
1745
|
+
async getFilterRule(ruleId) {
|
|
1746
|
+
return this.client.request(`/v1/twitter/stream/filter-rules/${ruleId}`);
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Partially update a filter rule.
|
|
1750
|
+
*
|
|
1751
|
+
* Only fields that are explicitly set in params are sent to the server.
|
|
1752
|
+
*
|
|
1753
|
+
* @param ruleId - UUID of the filter rule.
|
|
1754
|
+
* @param params - Fields to update (all optional).
|
|
1755
|
+
* @returns The updated FilterRuleResponse.
|
|
1756
|
+
* @throws NotFoundError - Rule not found for this API key.
|
|
1757
|
+
* @throws ValidationError - Invalid field values (422).
|
|
1758
|
+
*
|
|
1759
|
+
* @example
|
|
1760
|
+
* ```typescript
|
|
1761
|
+
* const rule = await client.twitter.stream.updateFilterRule("550e8400-...", {
|
|
1762
|
+
* interval_seconds: 120,
|
|
1763
|
+
* });
|
|
1764
|
+
* ```
|
|
1765
|
+
*/
|
|
1766
|
+
async updateFilterRule(ruleId, params) {
|
|
1767
|
+
const body = {};
|
|
1768
|
+
if (params.tag !== void 0) body["tag"] = params.tag;
|
|
1769
|
+
if (params.query !== void 0) body["query"] = params.query;
|
|
1770
|
+
if (params.interval_seconds !== void 0) body["interval_seconds"] = params.interval_seconds;
|
|
1771
|
+
if (params.status !== void 0) body["status"] = params.status;
|
|
1772
|
+
if (params.webhook_url !== void 0) body["webhook_url"] = params.webhook_url;
|
|
1773
|
+
if (params.webhook_secret !== void 0) body["webhook_secret"] = params.webhook_secret;
|
|
1774
|
+
if (params.max_results_per_poll !== void 0)
|
|
1775
|
+
body["max_results_per_poll"] = params.max_results_per_poll;
|
|
1776
|
+
return this.client.request(`/v1/twitter/stream/filter-rules/${ruleId}`, {
|
|
1777
|
+
method: "PATCH",
|
|
1778
|
+
body
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Delete a filter rule and all its associated logs. Irreversible.
|
|
1783
|
+
*
|
|
1784
|
+
* @param ruleId - UUID of the filter rule.
|
|
1785
|
+
* @throws NotFoundError - Rule not found for this API key.
|
|
1786
|
+
*
|
|
1787
|
+
* @example
|
|
1788
|
+
* ```typescript
|
|
1789
|
+
* await client.twitter.stream.deleteFilterRule("550e8400-...");
|
|
1790
|
+
* ```
|
|
1791
|
+
*/
|
|
1792
|
+
async deleteFilterRule(ruleId) {
|
|
1793
|
+
await this.client.request(`/v1/twitter/stream/filter-rules/${ruleId}`, {
|
|
1794
|
+
method: "DELETE"
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
// ===========================================================================
|
|
1798
|
+
// Filter Rules Utility
|
|
1799
|
+
// ===========================================================================
|
|
1800
|
+
/**
|
|
1801
|
+
* Validate a Twitter search query before creating a rule.
|
|
1802
|
+
*
|
|
1803
|
+
* @param query - The Twitter search query to validate.
|
|
1804
|
+
* @returns FilterRuleValidateResponse with validity and sample result count.
|
|
1805
|
+
*
|
|
1806
|
+
* @example
|
|
1807
|
+
* ```typescript
|
|
1808
|
+
* const result = await client.twitter.stream.validateFilterRuleQuery(
|
|
1809
|
+
* "#python lang:en -is:retweet"
|
|
1810
|
+
* );
|
|
1811
|
+
* if (!result.valid) {
|
|
1812
|
+
* console.error("Invalid query:", result.error);
|
|
1813
|
+
* }
|
|
1814
|
+
* ```
|
|
1815
|
+
*/
|
|
1816
|
+
async validateFilterRuleQuery(query) {
|
|
1817
|
+
return this.client.request(
|
|
1818
|
+
"/v1/twitter/stream/filter-rules/validate",
|
|
1819
|
+
{ method: "POST", body: { query } }
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* List tweet delivery logs for a specific filter rule.
|
|
1824
|
+
*
|
|
1825
|
+
* @param ruleId - UUID of the filter rule.
|
|
1826
|
+
* @param options - Filter and pagination options.
|
|
1827
|
+
* @returns FilterRuleDeliveryLogListResponse with pagination metadata.
|
|
1828
|
+
*
|
|
1829
|
+
* @example
|
|
1830
|
+
* ```typescript
|
|
1831
|
+
* const { logs, total } = await client.twitter.stream.listFilterRuleLogs(
|
|
1832
|
+
* "550e8400-...",
|
|
1833
|
+
* { limit: 50, deliveryStatus: "webhook_delivered" }
|
|
1834
|
+
* );
|
|
1835
|
+
* ```
|
|
1836
|
+
*/
|
|
1837
|
+
async listFilterRuleLogs(ruleId, options) {
|
|
1838
|
+
const params = {
|
|
1839
|
+
limit: options?.limit ?? 20,
|
|
1840
|
+
offset: options?.offset ?? 0,
|
|
1841
|
+
sort: options?.sort ?? "desc",
|
|
1842
|
+
delivery_status: options?.deliveryStatus
|
|
1843
|
+
};
|
|
1844
|
+
return this.client.request(
|
|
1845
|
+
`/v1/twitter/stream/filter-rules/${ruleId}/logs`,
|
|
1846
|
+
{ params }
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Get all available filter rule pricing tiers.
|
|
1851
|
+
*
|
|
1852
|
+
* @returns FilterRulePricingTiersResponse listing all tiers.
|
|
1853
|
+
*
|
|
1854
|
+
* @example
|
|
1855
|
+
* ```typescript
|
|
1856
|
+
* const { tiers } = await client.twitter.stream.getFilterRulePricingTiers();
|
|
1857
|
+
* tiers.forEach((t) => console.log(t.tier_label, t.credits_per_rule_per_day));
|
|
1858
|
+
* ```
|
|
1859
|
+
*/
|
|
1860
|
+
async getFilterRulePricingTiers() {
|
|
1861
|
+
return this.client.request(
|
|
1862
|
+
"/v1/twitter/stream/filter-rules-pricing"
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
// ===========================================================================
|
|
1866
|
+
// WebSocket Streaming -- EventEmitter style
|
|
1867
|
+
// ===========================================================================
|
|
1868
|
+
/**
|
|
1869
|
+
* Connect to the WebSocket stream and return an EventEmitter.
|
|
1870
|
+
*
|
|
1871
|
+
* The caller subscribes to events via `.on("tweet", handler)`.
|
|
1872
|
+
* The SDK handles pong replies to server pings automatically.
|
|
1873
|
+
* Call `.close()` on the emitter to disconnect cleanly.
|
|
1874
|
+
*
|
|
1875
|
+
* If reconnect is true, the emitter automatically reconnects after
|
|
1876
|
+
* disconnects (other than auth failures). A new "connected" event is
|
|
1877
|
+
* emitted on each reconnect.
|
|
1878
|
+
*
|
|
1879
|
+
* @param options - Connection options (reconnect, delay, maxReconnects).
|
|
1880
|
+
* @returns StreamEmitter -- an EventEmitter subclass.
|
|
1881
|
+
*
|
|
1882
|
+
* @example
|
|
1883
|
+
* ```typescript
|
|
1884
|
+
* const stream = client.twitter.stream.connect();
|
|
1885
|
+
* stream.on("connected", (e) => console.log("Connected:", e.connectionId));
|
|
1886
|
+
* stream.on("tweet", (event) => {
|
|
1887
|
+
* console.log(`@${event.authorUsername}: ${event.tweet.text}`);
|
|
1888
|
+
* console.log(` latency: ${event.latencyMs}ms`);
|
|
1889
|
+
* });
|
|
1890
|
+
* stream.on("error", (err) => console.error("Stream error:", err));
|
|
1891
|
+
* stream.on("close", () => console.log("Stream closed"));
|
|
1892
|
+
*
|
|
1893
|
+
* // Later:
|
|
1894
|
+
* stream.close();
|
|
1895
|
+
* ```
|
|
1896
|
+
*/
|
|
1897
|
+
connect(options = {}) {
|
|
1898
|
+
const { reconnect = false, reconnectDelaySeconds = 90, maxReconnects } = options;
|
|
1899
|
+
const delay = Math.max(MIN_RECONNECT_DELAY_SECONDS, reconnectDelaySeconds) * 1e3;
|
|
1900
|
+
const wsUrl = wsUrlFromBase(this.client.config.baseUrl);
|
|
1901
|
+
const apiKey = this.client.config.apiKey;
|
|
1902
|
+
const emitter = new events.EventEmitter();
|
|
1903
|
+
let ws = null;
|
|
1904
|
+
let closed = false;
|
|
1905
|
+
let reconnectCount = 0;
|
|
1906
|
+
const connectOnce = () => {
|
|
1907
|
+
ws = new WebSocket__default.default(wsUrl, { headers: { "x-api-key": apiKey } });
|
|
1908
|
+
ws.on("message", (data) => {
|
|
1909
|
+
let raw;
|
|
1910
|
+
try {
|
|
1911
|
+
raw = JSON.parse(String(data));
|
|
1912
|
+
} catch {
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
const event = parseEvent(raw);
|
|
1916
|
+
if (event.type === "ping") {
|
|
1917
|
+
ws?.send(JSON.stringify({ type: "pong" }));
|
|
1918
|
+
emitter.emit("ping", event);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
if (event.type === "error") {
|
|
1922
|
+
const code = event.code;
|
|
1923
|
+
const err = new WebSocketStreamError(event.message, code);
|
|
1924
|
+
emitter.emit("error", err);
|
|
1925
|
+
if (code === 4001 || code === 4003) {
|
|
1926
|
+
closed = true;
|
|
1927
|
+
ws?.close();
|
|
1928
|
+
}
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
emitter.emit(event.type, event);
|
|
1932
|
+
});
|
|
1933
|
+
ws.on("open", () => {
|
|
1934
|
+
});
|
|
1935
|
+
ws.on("close", (code, reason) => {
|
|
1936
|
+
if (closed) {
|
|
1937
|
+
emitter.emit("close");
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (!reconnect) {
|
|
1941
|
+
const reasonStr = reason instanceof Buffer ? reason.toString() : String(reason ?? "");
|
|
1942
|
+
emitter.emit(
|
|
1943
|
+
"error",
|
|
1944
|
+
new WebSocketStreamError(`WebSocket closed: ${reasonStr || String(code)}`)
|
|
1945
|
+
);
|
|
1946
|
+
emitter.emit("close");
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (maxReconnects !== void 0 && reconnectCount >= maxReconnects) {
|
|
1950
|
+
emitter.emit(
|
|
1951
|
+
"error",
|
|
1952
|
+
new WebSocketStreamError(`Max reconnects (${maxReconnects}) exhausted`)
|
|
1953
|
+
);
|
|
1954
|
+
emitter.emit("close");
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
reconnectCount++;
|
|
1958
|
+
setTimeout(connectOnce, delay);
|
|
1959
|
+
});
|
|
1960
|
+
ws.on("error", (err) => {
|
|
1961
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1962
|
+
emitter.emit("error", new WebSocketStreamError(message));
|
|
1963
|
+
});
|
|
1964
|
+
};
|
|
1965
|
+
emitter.close = () => {
|
|
1966
|
+
closed = true;
|
|
1967
|
+
ws?.close(1e3, "Client closed");
|
|
1968
|
+
};
|
|
1969
|
+
connectOnce();
|
|
1970
|
+
return emitter;
|
|
1971
|
+
}
|
|
1972
|
+
// ===========================================================================
|
|
1973
|
+
// WebSocket Streaming -- AsyncIterator style
|
|
1974
|
+
// ===========================================================================
|
|
1975
|
+
/**
|
|
1976
|
+
* Connect to the WebSocket stream and return an AsyncIterator.
|
|
1977
|
+
*
|
|
1978
|
+
* Iterates over StreamEvent objects. The SDK handles pong replies
|
|
1979
|
+
* automatically but still yields PingEvent to the caller (the caller
|
|
1980
|
+
* may ignore ping events).
|
|
1981
|
+
*
|
|
1982
|
+
* @param options - Connection options (reconnect, delay, maxReconnects).
|
|
1983
|
+
* @yields StreamEvent
|
|
1984
|
+
*
|
|
1985
|
+
* @example
|
|
1986
|
+
* ```typescript
|
|
1987
|
+
* for await (const event of client.twitter.stream.connectIter()) {
|
|
1988
|
+
* if (event.type === "tweet") {
|
|
1989
|
+
* console.log(`@${event.authorUsername}: ${event.latencyMs}ms`);
|
|
1990
|
+
* }
|
|
1991
|
+
* }
|
|
1992
|
+
* ```
|
|
1993
|
+
*
|
|
1994
|
+
* @example With auto-reconnect
|
|
1995
|
+
* ```typescript
|
|
1996
|
+
* for await (const event of client.twitter.stream.connectIter({
|
|
1997
|
+
* reconnect: true,
|
|
1998
|
+
* reconnectDelaySeconds: 90,
|
|
1999
|
+
* })) {
|
|
2000
|
+
* if (event.type === "tweet") {
|
|
2001
|
+
* // process(event);
|
|
2002
|
+
* }
|
|
2003
|
+
* }
|
|
2004
|
+
* ```
|
|
2005
|
+
*/
|
|
2006
|
+
async *connectIter(options = {}) {
|
|
2007
|
+
const { reconnect = false, reconnectDelaySeconds = 90, maxReconnects } = options;
|
|
2008
|
+
const delay = Math.max(MIN_RECONNECT_DELAY_SECONDS, reconnectDelaySeconds) * 1e3;
|
|
2009
|
+
const wsUrl = wsUrlFromBase(this.client.config.baseUrl);
|
|
2010
|
+
const apiKey = this.client.config.apiKey;
|
|
2011
|
+
let reconnectCount = 0;
|
|
2012
|
+
while (true) {
|
|
2013
|
+
const events = [];
|
|
2014
|
+
let resolveWait = null;
|
|
2015
|
+
let rejectWait = null;
|
|
2016
|
+
let done = false;
|
|
2017
|
+
const ws = new WebSocket__default.default(wsUrl, { headers: { "x-api-key": apiKey } });
|
|
2018
|
+
const waitForEvent = () => new Promise((res, rej) => {
|
|
2019
|
+
resolveWait = res;
|
|
2020
|
+
rejectWait = rej;
|
|
2021
|
+
});
|
|
2022
|
+
ws.on("message", (data) => {
|
|
2023
|
+
let raw;
|
|
2024
|
+
try {
|
|
2025
|
+
raw = JSON.parse(String(data));
|
|
2026
|
+
} catch {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const event = parseEvent(raw);
|
|
2030
|
+
if (event.type === "ping") {
|
|
2031
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
2032
|
+
}
|
|
2033
|
+
if (event.type === "error") {
|
|
2034
|
+
const code = event.code;
|
|
2035
|
+
if (code === 4001 || code === 4003) {
|
|
2036
|
+
rejectWait?.(new WebSocketStreamError(event.message, code));
|
|
2037
|
+
rejectWait = null;
|
|
2038
|
+
resolveWait = null;
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
events.push(event);
|
|
2043
|
+
resolveWait?.();
|
|
2044
|
+
resolveWait = null;
|
|
2045
|
+
rejectWait = null;
|
|
2046
|
+
});
|
|
2047
|
+
ws.on("close", () => {
|
|
2048
|
+
done = true;
|
|
2049
|
+
resolveWait?.();
|
|
2050
|
+
resolveWait = null;
|
|
2051
|
+
rejectWait = null;
|
|
2052
|
+
});
|
|
2053
|
+
ws.on("error", (err) => {
|
|
2054
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2055
|
+
rejectWait?.(new WebSocketStreamError(message));
|
|
2056
|
+
rejectWait = null;
|
|
2057
|
+
resolveWait = null;
|
|
2058
|
+
});
|
|
2059
|
+
try {
|
|
2060
|
+
while (!done || events.length > 0) {
|
|
2061
|
+
if (events.length === 0) {
|
|
2062
|
+
await waitForEvent();
|
|
2063
|
+
}
|
|
2064
|
+
while (events.length > 0) {
|
|
2065
|
+
yield events.shift();
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
} catch (err) {
|
|
2069
|
+
ws.close();
|
|
2070
|
+
throw err;
|
|
2071
|
+
} finally {
|
|
2072
|
+
ws.close();
|
|
2073
|
+
}
|
|
2074
|
+
if (!reconnect) {
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
if (maxReconnects !== void 0 && reconnectCount >= maxReconnects) {
|
|
2078
|
+
throw new WebSocketStreamError(`Max reconnects (${maxReconnects}) exhausted`);
|
|
2079
|
+
}
|
|
2080
|
+
reconnectCount++;
|
|
2081
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
function verifyWebhookSignature(secret, body, signatureHeader) {
|
|
2086
|
+
if (!signatureHeader.startsWith("sha256=")) {
|
|
2087
|
+
return false;
|
|
2088
|
+
}
|
|
2089
|
+
const expectedHex = signatureHeader.slice("sha256=".length);
|
|
2090
|
+
const bodyBuffer = typeof body === "string" ? Buffer.from(body, "utf-8") : body;
|
|
2091
|
+
const actualHex = crypto.createHmac("sha256", secret).update(bodyBuffer).digest("hex");
|
|
2092
|
+
try {
|
|
2093
|
+
return crypto.timingSafeEqual(Buffer.from(expectedHex, "hex"), Buffer.from(actualHex, "hex"));
|
|
2094
|
+
} catch {
|
|
2095
|
+
return false;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1412
2098
|
|
|
1413
2099
|
// src/twitter/client.ts
|
|
1414
2100
|
var TwitterClient = class {
|
|
@@ -1424,6 +2110,8 @@ var TwitterClient = class {
|
|
|
1424
2110
|
trends;
|
|
1425
2111
|
/** Client for geo/places operations */
|
|
1426
2112
|
geo;
|
|
2113
|
+
/** Client for real-time stream monitor management and WebSocket streaming */
|
|
2114
|
+
stream;
|
|
1427
2115
|
/**
|
|
1428
2116
|
* Create a new Twitter client.
|
|
1429
2117
|
*
|
|
@@ -1436,6 +2124,7 @@ var TwitterClient = class {
|
|
|
1436
2124
|
this.communities = new CommunitiesClient(client);
|
|
1437
2125
|
this.trends = new TrendsClient(client);
|
|
1438
2126
|
this.geo = new GeoClient(client);
|
|
2127
|
+
this.stream = new StreamClient(client);
|
|
1439
2128
|
}
|
|
1440
2129
|
};
|
|
1441
2130
|
|
|
@@ -1587,6 +2276,7 @@ var ScrapeBadger = class {
|
|
|
1587
2276
|
exports.AccountRestrictedError = AccountRestrictedError;
|
|
1588
2277
|
exports.AuthenticationError = AuthenticationError;
|
|
1589
2278
|
exports.CommunitiesClient = CommunitiesClient;
|
|
2279
|
+
exports.ConflictError = ConflictError;
|
|
1590
2280
|
exports.GeoClient = GeoClient;
|
|
1591
2281
|
exports.InsufficientCreditsError = InsufficientCreditsError;
|
|
1592
2282
|
exports.ListsClient = ListsClient;
|
|
@@ -1595,6 +2285,7 @@ exports.RateLimitError = RateLimitError;
|
|
|
1595
2285
|
exports.ScrapeBadger = ScrapeBadger;
|
|
1596
2286
|
exports.ScrapeBadgerError = ScrapeBadgerError;
|
|
1597
2287
|
exports.ServerError = ServerError;
|
|
2288
|
+
exports.StreamClient = StreamClient;
|
|
1598
2289
|
exports.TimeoutError = TimeoutError;
|
|
1599
2290
|
exports.TrendsClient = TrendsClient;
|
|
1600
2291
|
exports.TweetsClient = TweetsClient;
|
|
@@ -1602,6 +2293,8 @@ exports.TwitterClient = TwitterClient;
|
|
|
1602
2293
|
exports.UsersClient = UsersClient;
|
|
1603
2294
|
exports.ValidationError = ValidationError;
|
|
1604
2295
|
exports.WebClient = WebClient;
|
|
2296
|
+
exports.WebSocketStreamError = WebSocketStreamError;
|
|
1605
2297
|
exports.collectAll = collectAll;
|
|
2298
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
1606
2299
|
//# sourceMappingURL=index.js.map
|
|
1607
2300
|
//# sourceMappingURL=index.js.map
|