emusks 2.0.18 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  <img src="https://emusks.tiago.zip/icon.svg" width="120">
2
2
  <h1>emusks: Reverse-engineered Twitter API client</h1>
3
3
 
4
- Log in and interact with the unofficial X API using any client identity web, Android, iOS, or TweetDeck
4
+ Log in and interact with the unofficial X API using any client identity - web, Android, iOS, or TweetDeck. Covers tweets, users, DMs, communities, spaces, and XChat (X's end-to-end encrypted chat): send encrypted DMs in one call.
5
5
 
6
6
  officially dmca'd by twitter™ 🏆 • includes a few leaked ads bearers
7
7
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "emusks",
3
- "version": "2.0.18",
4
- "description": "Reverse-engineered Twitter API client. Log in and interact with the unofficial X API using any client identity web, Android, iOS, or TweetDeck",
3
+ "version": "2.1.1",
4
+ "description": "Reverse-engineered Twitter API client. Log in and interact with the unofficial X API using any client identity - web, Android, iOS, or TweetDeck",
5
5
  "keywords": [
6
6
  "client",
7
7
  "reverse-engineering",
@@ -24,8 +24,25 @@
24
24
  "exports": {
25
25
  ".": "./src/index.js"
26
26
  },
27
+ "files": [
28
+ "src",
29
+ "README.md"
30
+ ],
27
31
  "dependencies": {
28
32
  "cycletls": "^2.0.5",
33
+ "linkedom": "^0.18.12",
34
+ "tweetnacl": "^1.0.3",
29
35
  "x-client-transaction-id": "^0.2.0"
36
+ },
37
+ "peerDependencies": {
38
+ "@roamhq/wrtc": "^0.10.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "@roamhq/wrtc": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@roamhq/wrtc": "^0.10.0"
30
47
  }
31
48
  }
package/src/cycletls.js CHANGED
@@ -2,7 +2,8 @@ import initCycleTLS from "cycletls";
2
2
 
3
3
  let cycleTLS;
4
4
 
5
- initCycleTLS().then((c) => {
5
+ const port = process.env.CYCLETLS_PORT ? Number(process.env.CYCLETLS_PORT) : undefined;
6
+ initCycleTLS(port ? { port } : {}).then((c) => {
6
7
  cycleTLS = c;
7
8
  });
8
9
 
package/src/flow.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import getCycleTLS from "./cycletls.js";
2
2
  import clients from "./clients.js";
3
+ import { solveJsInstrumentation } from "./instrumentation.js";
3
4
 
4
5
  const BASE_URL = "https://api.x.com/1.1/onboarding/task.json";
5
6
  const GUEST_ACTIVATE_URL = "https://api.x.com/1.1/guest/activate.json";
@@ -103,6 +104,33 @@ class CookieSession {
103
104
 
104
105
  return response;
105
106
  }
107
+
108
+ async get(url, options = {}) {
109
+ const headers = options.headers || {};
110
+ const cookieString = this.getCookieString();
111
+ if (cookieString) {
112
+ headers["Cookie"] = cookieString;
113
+ }
114
+
115
+ const response = await this.cycleTLS(
116
+ url,
117
+ {
118
+ ja3: clients.web.fingerprints.ja3,
119
+ ja4r: clients.web.fingerprints.ja4r,
120
+ userAgent: USER_AGENT,
121
+ headers,
122
+ proxy: this.proxy || undefined,
123
+ },
124
+ "get",
125
+ );
126
+
127
+ const setCookie = response.headers?.["Set-Cookie"] || response.headers?.["set-cookie"];
128
+ if (setCookie) {
129
+ this.parseCookies(setCookie);
130
+ }
131
+
132
+ return response;
133
+ }
106
134
  }
107
135
 
108
136
  function getFlowHeaders(guestToken) {
@@ -200,12 +228,39 @@ async function initFlow(session, guestToken) {
200
228
  return [flowToken, headers, data];
201
229
  }
202
230
 
203
- async function submitJsInstrumentation(session, flowToken, headers) {
231
+ const DEFAULT_JS_INST_URL = "https://twitter.com/i/js_inst?c_name=ui_metrics";
232
+
233
+ async function fetchChallengeJs(session, url) {
234
+ const response = await session.get(url, {
235
+ Accept: "*/*",
236
+ "Accept-Language": "en-US,en;q=0.9",
237
+ Referer: "https://x.com/",
238
+ "User-Agent": USER_AGENT,
239
+ });
240
+
241
+ if (response.status !== 200) {
242
+ throw new Error(`Failed to fetch js_instrumentation challenge: ${response.status}`);
243
+ }
244
+
245
+ let body = response.body ?? response.data;
246
+ if (body && typeof body === "object") {
247
+ body = Buffer.from(body.data ?? body).toString("utf8");
248
+ }
249
+ if (typeof body !== "string" || body.length === 0) {
250
+ throw new Error("js_instrumentation challenge returned an empty body");
251
+ }
252
+ return body;
253
+ }
254
+
255
+ async function submitJsInstrumentation(session, flowToken, headers, subtask) {
256
+ const url = subtask?.js_instrumentation?.url || DEFAULT_JS_INST_URL;
257
+ const challengeJs = await fetchChallengeJs(session, url);
258
+ const response = solveJsInstrumentation(challengeJs);
259
+
204
260
  return await makeRequest(session, headers, flowToken, {
205
261
  subtask_id: "LoginJsInstrumentationSubtask",
206
262
  js_instrumentation: {
207
- response:
208
- '{"rf":{"a4fc506d24bb4843c48a1966940c2796bf4fb7617a2d515ad3297b7df6b459b6":121,"bff66e16f1d7ea28c04653dc32479cf416a9c8b67c80cb8ad533b2a44fee82a3":-1,"ac4008077a7e6ca03210159dbe2134dea72a616f03832178314bb9931645e4f7":-22,"c3a8a81a9b2706c6fec42c771da65a9597c537b8e4d9b39e8e58de9fe31ff239":-12},"s":"ZHYaDA9iXRxOl2J3AZ9cc23iJx-Fg5E82KIBA_fgeZFugZGYzRtf8Bl3EUeeYgsK30gLFD2jTQx9fAMsnYCw0j8ahEy4Pb5siM5zD6n7YgOeWmFFaXoTwaGY4H0o-jQnZi5yWZRAnFi4lVuCVouNz_xd2BO2sobCO7QuyOsOxQn2CWx7bjD8vPAzT5BS1mICqUWyjZDjLnRZJU6cSQG5YFIHEPBa8Kj-v1JFgkdAfAMIdVvP7C80HWoOqYivQR7IBuOAI4xCeLQEdxlGeT-JYStlP9dcU5St7jI6ExyMeQnRicOcxXLXsan8i5Joautk2M8dAJFByzBaG4wtrPhQ3QAAAZEi-_t7"}',
263
+ response,
209
264
  link: "next_link",
210
265
  },
211
266
  });
@@ -306,7 +361,8 @@ async function resolve(staticValue, onRequest, type) {
306
361
  }
307
362
 
308
363
  export default async function flowLogin(opts) {
309
- const { username, password, email, phone, onRequest, proxy } = opts;
364
+ const { username, password, email, phone, onRequest, proxy, debug } = opts;
365
+ const log = debug ? (...a) => console.error("[flow]", ...a) : () => {};
310
366
 
311
367
  if (!username) throw new Error("username is required for flow login");
312
368
  if (!password) throw new Error("password is required for flow login");
@@ -315,6 +371,7 @@ export default async function flowLogin(opts) {
315
371
  const session = new CookieSession(cycleTLS, proxy);
316
372
 
317
373
  const guestToken = await getGuestToken(session);
374
+ log("guest token", guestToken);
318
375
 
319
376
  let [flowToken, headers, data] = await initFlow(session, guestToken);
320
377
  headers["X-Guest-Token"] = guestToken;
@@ -324,16 +381,23 @@ export default async function flowLogin(opts) {
324
381
  while (!isLoginComplete(data) && step < MAX_FLOW_STEPS) {
325
382
  step++;
326
383
  const subtaskIds = getSubtaskIds(data);
384
+ log(`step ${step}: subtasks [${subtaskIds.join(", ")}]`);
327
385
 
328
386
  if (subtaskIds.length === 0) {
329
387
  throw new Error("Login flow returned no subtasks and login is not complete");
330
388
  }
331
389
 
332
390
  const current = subtaskIds[0];
391
+ const currentSubtask = data.subtasks.find((s) => s.subtask_id === current);
333
392
 
334
393
  switch (current) {
335
394
  case "LoginJsInstrumentationSubtask":
336
- [flowToken, data] = await submitJsInstrumentation(session, flowToken, headers);
395
+ [flowToken, data] = await submitJsInstrumentation(
396
+ session,
397
+ flowToken,
398
+ headers,
399
+ currentSubtask,
400
+ );
337
401
  break;
338
402
 
339
403
  case "LoginEnterUserIdentifierSSO":
package/src/graphql.js CHANGED
@@ -141,7 +141,11 @@ export default async function graphql(queryName, { variables, fieldToggles, body
141
141
  ).json();
142
142
 
143
143
  if (res?.errors?.[0]) {
144
- throw new Error(res.errors.map((err) => err.message).join(", "));
144
+ const hasData =
145
+ res.data != null && typeof res.data === "object" && Object.keys(res.data).length > 0;
146
+ if (!hasData) {
147
+ throw new Error(res.errors.map((err) => err.message).join(", "));
148
+ }
145
149
  }
146
150
 
147
151
  return res;
@@ -2,6 +2,7 @@ import * as account from "./account.js";
2
2
  import * as bookmarks from "./bookmarks.js";
3
3
  import * as communities from "./communities.js";
4
4
  import * as dms from "./dms.js";
5
+ import * as jetfuel from "./jetfuel.js";
5
6
  import * as lists from "./lists.js";
6
7
  import * as media from "./media.js";
7
8
  import * as notifications from "./notifications.js";
@@ -13,6 +14,7 @@ import * as topics from "./topics.js";
13
14
  import * as trends from "./trends.js";
14
15
  import * as tweets from "./tweets.js";
15
16
  import * as users from "./users.js";
17
+ import * as xchat from "./xchat.js";
16
18
 
17
19
  function namespace(proto, name, methods) {
18
20
  Object.defineProperty(proto, name, {
@@ -48,4 +50,6 @@ export default function initHelpers(proto) {
48
50
  namespace(proto, "topics", topics);
49
51
  namespace(proto, "media", media);
50
52
  namespace(proto, "syndication", syndication);
53
+ namespace(proto, "xchat", xchat);
54
+ namespace(proto, "jetfuel", jetfuel);
51
55
  }
@@ -0,0 +1,175 @@
1
+ import {
2
+ buildTimelineToken,
3
+ decodeJetfuel,
4
+ decodeTimelineToken,
5
+ extractTimelineTokens,
6
+ } from "../parsers/jetfuel.js";
7
+ import parseTimeline from "../parsers/timeline.js";
8
+
9
+ export const ENGAGEMENTS = ["Likes", "Replies", "Bookmarks", "Quotes", "VideoQualityViews"];
10
+
11
+ export const PERIODS = ["Daily", "Weekly", "Monthly"];
12
+
13
+ export const COUNTRIES = [
14
+ "All", "USA", "CAN", "GBR", "AUS", "DEU", "FRA", "JPN", "BRA", "IND", "MEX",
15
+ "ESP", "ITA", "KOR", "NLD", "ARG", "POL", "TUR", "IDN", "SAU", "ZAF", "NGA",
16
+ "PHL", "THA", "VNM", "EGY", "PAK", "MYS", "SGP", "ARE", "COL", "CHL", "SWE",
17
+ "NOR", "DNK", "FIN", "BEL", "AUT", "CHE", "PRT", "GRC", "IRL", "NZL", "ISR",
18
+ "RUS", "UKR", "CZE", "ROU", "HUN",
19
+ ];
20
+
21
+ function assertEnum(value, allowed, label) {
22
+ if (!allowed.includes(value)) {
23
+ throw new Error(`invalid ${label} "${value}", expected one of: ${allowed.join(", ")}`);
24
+ }
25
+ }
26
+
27
+ export async function page(route, opts = {}) {
28
+ const res = await this.jf(route, opts);
29
+ return { ...decodeJetfuel(res.buffer), status: res.status, contentType: res.contentType };
30
+ }
31
+
32
+ export async function remote(route, opts = {}) {
33
+ return await page.call(this, route, opts);
34
+ }
35
+
36
+ export async function raw(route, opts = {}) {
37
+ return await this.jf(route, opts);
38
+ }
39
+
40
+ export function decode(buffer) {
41
+ return decodeJetfuel(buffer);
42
+ }
43
+
44
+ export function topPostsTimelineId({ engagement = "Likes", period = "Daily", country = "All" } = {}) {
45
+ assertEnum(engagement, ENGAGEMENTS, "engagement");
46
+ assertEnum(period, PERIODS, "period");
47
+ return buildTimelineToken({ engagement, country, period });
48
+ }
49
+
50
+ export async function topPostsFeed({ engagement = "Likes", period = "Daily", country = "All" } = {}) {
51
+ assertEnum(engagement, ENGAGEMENTS, "engagement");
52
+ assertEnum(period, PERIODS, "period");
53
+
54
+ const params = { engagement, period };
55
+ if (country && country !== "All") params.country = country;
56
+
57
+ const res = await this.jf("creators/inspiration/remote/urt", { params });
58
+ const tokens = extractTimelineTokens(res.buffer);
59
+ const token = tokens[0] || null;
60
+
61
+ return {
62
+ status: res.status,
63
+ token,
64
+ params: token ? decodeTimelineToken(token) : null,
65
+ decoded: decodeJetfuel(res.buffer),
66
+ };
67
+ }
68
+
69
+ export async function topPosts(opts = {}) {
70
+ const { engagement = "Likes", period = "Daily", country = "All", count = 20, cursor, viaRemote, raw: returnRaw } = opts;
71
+ assertEnum(engagement, ENGAGEMENTS, "engagement");
72
+ assertEnum(period, PERIODS, "period");
73
+
74
+ let timelineId;
75
+ if (viaRemote) {
76
+ const feed = await topPostsFeed.call(this, { engagement, period, country });
77
+ if (!feed.token) throw new Error("inspiration feed returned no timeline token");
78
+ timelineId = feed.token;
79
+ } else {
80
+ timelineId = buildTimelineToken({ engagement, country, period });
81
+ }
82
+
83
+ const variables = {
84
+ timelineId,
85
+ count,
86
+ withQuickPromoteEligibilityTweetFields: true,
87
+ };
88
+ if (cursor) variables.cursor = cursor;
89
+
90
+ const res = await this.graphql("GenericTimelineById", { variables });
91
+ if (returnRaw) return res;
92
+
93
+ const parsed = parseTimeline(res);
94
+ return { ...parsed, engagement, period, country, timelineId };
95
+ }
96
+
97
+ export async function inspirationPage(opts = {}) {
98
+ return await page.call(this, "creators/inspiration/top_posts", opts);
99
+ }
100
+
101
+ export async function stories(opts = {}) {
102
+ return await page.call(this, "stories/home", opts);
103
+ }
104
+
105
+ export async function storiesRemote({ category } = {}, opts = {}) {
106
+ const params = { ...opts.params };
107
+ if (category) params.category = category;
108
+ return await remote.call(this, "stories/storiesRemote", { ...opts, params });
109
+ }
110
+
111
+ export async function newsArticle(id, opts = {}) {
112
+ if (!id) throw new Error("newsArticle requires an article id");
113
+ return await page.call(this, `news/article/id/${id}`, opts);
114
+ }
115
+
116
+ export const SPORT_EVENTS = {
117
+ nba: { noun: "game", home: true },
118
+ nfl: { noun: "game", home: true },
119
+ f1: { noun: "race", home: true },
120
+ nhl: { noun: "game", home: false },
121
+ soccer: { noun: "match", home: false },
122
+ };
123
+
124
+ export async function sportHome(sport, opts = {}) {
125
+ if (!sport) throw new Error("sportHome requires a sport");
126
+ return await page.call(this, `${sport}/home`, opts);
127
+ }
128
+
129
+ export async function league(sport, opts = {}) {
130
+ if (!sport) throw new Error("league requires a sport");
131
+ return await page.call(this, `${sport}/league/home`, opts);
132
+ }
133
+
134
+ export async function game({ sport, id, noun, home } = {}, opts = {}) {
135
+ if (!sport || !id) throw new Error("game requires { sport, id }");
136
+ const shape = SPORT_EVENTS[sport] || {};
137
+ const eventNoun = noun ?? shape.noun ?? "game";
138
+ const hasHome = home ?? shape.home ?? false;
139
+ const route = `${sport}/${eventNoun}${hasHome ? "/home" : ""}/id/${id}`;
140
+ return await page.call(this, route, opts);
141
+ }
142
+
143
+ export async function event(id, opts = {}) {
144
+ if (!id) throw new Error("event requires an id");
145
+ return await page.call(this, "events/event", { ...opts, params: { id, ...opts.params } });
146
+ }
147
+
148
+ export async function brackets(opts = {}) {
149
+ return await page.call(this, "brackets/home", opts);
150
+ }
151
+
152
+ export async function bracketView(userId, opts = {}) {
153
+ return await page.call(this, userId ? `brackets/view/id/${userId}` : "brackets/view", opts);
154
+ }
155
+
156
+ export async function creatorsStudio(opts = {}) {
157
+ return await page.call(this, "creators/studio", opts);
158
+ }
159
+
160
+ export async function onboardingTopics(opts = {}) {
161
+ return await remote.call(this, "onboarding/remotes/topics", opts);
162
+ }
163
+
164
+ export async function health(opts = {}) {
165
+ const res = await this.jf("health", { ...opts, origin: "jf" });
166
+ return res.text();
167
+ }
168
+
169
+ export function ogImageUrl(tweetId) {
170
+ return `https://jf.x.com/images/post/${tweetId}`;
171
+ }
172
+
173
+ export function mediaPreviewUrl(tweetId) {
174
+ return `https://jf.x.com/images/media-preview/${tweetId}`;
175
+ }
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=e=>a.call(e,`module.exports`)?e[`module.exports`]:l(t({},`__esModule`,{value:!0}),e);export{u as a,d as i,o as n,c as r,s as t};
@@ -0,0 +1,30 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const dir = dirname(fileURLToPath(import.meta.url));
6
+ const wasmPath = join(dir, "juicebox-sdk_bg.wasm");
7
+
8
+ let authTokenResolver = () => {
9
+ throw new Error("juicebox: auth-token resolver not set");
10
+ };
11
+ export function setAuthTokenResolver(fn) {
12
+ authTokenResolver = fn;
13
+ }
14
+
15
+ globalThis.JuiceboxGetAuthToken = (realmId) => Promise.resolve(authTokenResolver(realmId));
16
+
17
+ const origFetch = globalThis.fetch.bind(globalThis);
18
+ globalThis.fetch = (input, init) => {
19
+ const url = typeof input === "string" ? input : (input?.url ?? String(input));
20
+ if (url.includes("juicebox-sdk_bg") && url.endsWith(".wasm")) {
21
+ return Promise.resolve(
22
+ new Response(readFileSync(wasmPath), { headers: { "Content-Type": "application/wasm" } }),
23
+ );
24
+ }
25
+ return origFetch(input, init);
26
+ };
27
+
28
+ const glue = await import("./sdk.js");
29
+
30
+ export const { Client, Configuration, RecoverError, RecoverErrorReason } = glue;
@@ -0,0 +1 @@
1
+ import{n as e,r as t}from"./chunk-BNv3lrIs.js";var n,r=e((()=>{n=`/assets/juicebox-sdk_bg-B54z_Bsk.wasm`})),i,a=e((()=>{i=async(e={},t)=>{let n;if(t.startsWith(`data:`)){let r=t.replace(/^data:.*?base64,/,``),i;if(typeof Buffer==`function`&&typeof Buffer.from==`function`)i=Buffer.from(r,`base64`);else if(typeof atob==`function`){let e=atob(r);i=new Uint8Array(e.length);for(let t=0;t<e.length;t++)i[t]=e.charCodeAt(t)}else throw Error(`Cannot decode base64-encoded data URL`);n=await WebAssembly.instantiate(i,e)}else{let r=await fetch(t),i=r.headers.get(`Content-Type`)||``;if(`instantiateStreaming`in WebAssembly&&i.startsWith(`application/wasm`))n=await WebAssembly.instantiateStreaming(r,e);else{let t=await r.arrayBuffer();n=await WebAssembly.instantiate(t,e)}}return n.instance.exports}}));function o(e){V=e}function s(e){return H[e]}function c(e){e<132||(H[e]=U,U=e)}function l(e){let t=s(e);return c(e),t}function u(){return(G===null||G.byteLength===0)&&(G=new Uint8Array(V.memory.buffer)),G}function d(e,t,n){if(n===void 0){let n=K.encode(e),r=t(n.length,1)>>>0;return u().subarray(r,r+n.length).set(n),W=n.length,r}let r=e.length,i=t(r,1)>>>0,a=u(),o=0;for(;o<r;o++){let t=e.charCodeAt(o);if(t>127)break;a[i+o]=t}if(o!==r){o!==0&&(e=e.slice(o)),i=n(i,r,r=o+e.length*3,1)>>>0;let t=u().subarray(i+o,i+r),a=mt(e,t);o+=a.written}return W=o,i}function f(e){return e==null}function p(){return(q===null||q.byteLength===0)&&(q=new Int32Array(V.memory.buffer)),q}function m(e,t){return e>>>=0,J.decode(u().subarray(e,e+t))}function h(e){U===H.length&&H.push(H.length+1);let t=U;return U=H[t],H[t]=e,t}function ee(){return(Y===null||Y.byteLength===0)&&(Y=new Float64Array(V.memory.buffer)),Y}function g(e){let t=typeof e;if(t==`number`||t==`boolean`||e==null)return`${e}`;if(t==`string`)return`"${e}"`;if(t==`symbol`){let t=e.description;return t==null?`Symbol`:`Symbol(${t})`}if(t==`function`){let t=e.name;return typeof t==`string`&&t.length>0?`Function(${t})`:`Function`}if(Array.isArray(e)){let t=e.length,n=`[`;t>0&&(n+=g(e[0]));for(let r=1;r<t;r++)n+=`, `+g(e[r]);return n+=`]`,n}let n=/\[object ([^\]]+)\]/.exec(toString.call(e)),r;if(n.length>1)r=n[1];else return toString.call(e);if(r==`Object`)try{return`Object(`+JSON.stringify(e)+`)`}catch{return`Object`}return e instanceof Error?`${e.name}: ${e.message}\n${e.stack}`:r}function te(e,t,n,r){let i={a:e,b:t,cnt:1,dtor:n},a=(...e)=>{i.cnt++;let t=i.a;i.a=0;try{return r(t,i.b,...e)}finally{--i.cnt===0?V.__wbindgen_export_2.get(i.dtor)(t,i.b):i.a=t}};return a.original=i,a}function ne(e,t){V.wasm_bindgen__convert__closures__invoke0_mut__hcf60f8fe499c96ac(e,t)}function re(e,t,n){V._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h664375554e075ae3(e,t,h(n))}function ie(e,t){if(!(e instanceof t))throw Error(`expected instance of ${t.name}`);return e.ptr}function _(e,t){let n=t(e.length*1,1)>>>0;return u().set(e,n/1),W=e.length,n}function v(e,t){try{return e.apply(this,t)}catch(e){V.__wbindgen_exn_store(h(e))}}function ae(e,t,n,r){V.wasm_bindgen__convert__closures__invoke2_mut__h35a3737c9c8313d7(e,t,h(n),h(r))}function oe(e){l(e)}function se(e){return h(Z.__wrap(e))}function ce(e){let t=l(e).original;return t.cnt--==1?(t.a=0,!0):!1}function le(e,t){let n=s(t),r=typeof n==`string`?n:void 0;var i=f(r)?0:d(r,V.__wbindgen_malloc,V.__wbindgen_realloc),a=W;p()[e/4+1]=a,p()[e/4+0]=i}function ue(e){return typeof s(e)==`string`}function de(e){let t=s(e);return typeof t==`object`&&!!t}function fe(e){return s(e)===void 0}function pe(e,t){return s(e)in s(t)}function y(e,t){return h(Error(m(e,t)))}function b(e){return h(fetch(s(e)))}function x(){return v(function(e,t){return h(setTimeout(s(e),t))},arguments)}function S(){return v(function(e){return h(JuiceboxGetAuthToken(l(e)))},arguments)}function C(){return h(Error())}function w(e,t){let n=s(t).stack,r=d(n,V.__wbindgen_malloc,V.__wbindgen_realloc),i=W;p()[e/4+1]=i,p()[e/4+0]=r}function T(e,t){let n,r;try{n=e,r=t,console.error(m(e,t))}finally{V.__wbindgen_free(n,r,1)}}function E(e){queueMicrotask(s(e))}function D(e){let t=s(e).queueMicrotask;return h(t)}function O(e){return typeof s(e)==`function`}function k(e){return h(s(e))}function A(e,t){return s(e)==s(t)}function j(e){let t=s(e);return typeof t==`boolean`?+!!t:2}function M(e,t){let n=s(t),r=typeof n==`number`?n:void 0;ee()[e/8+1]=f(r)?0:r,p()[e/4+0]=!f(r)}function N(e){return+s(e)}function P(e){return h(e)}function F(e,t){return h(m(e,t))}function I(e,t){let n=s(e)[s(t)];return h(n)}function L(){return Date.now()}function R(e){return s(e).now()}function z(e){let t=s(e).headers;return h(t)}function B(){return v(function(e,t,n){return h(new Request(m(e,t),s(n)))},arguments)}function me(){return v(function(e,t,n,r,i){s(e).set(m(t,n),m(r,i))},arguments)}function he(e){return h(s(e).arrayBuffer())}function ge(e){let t;try{t=s(e)instanceof Response}catch{t=!1}return t}function _e(e){return s(e).status}function ve(e){let t=s(e).headers;return h(t)}function ye(){return v(function(e){return h(s(e).blob())},arguments)}function be(e){let t=s(e).crypto;return h(t)}function xe(e){let t=s(e).process;return h(t)}function Se(e){let t=s(e).versions;return h(t)}function Ce(e){let t=s(e).node;return h(t)}function we(e){let t=s(e).msCrypto;return h(t)}function Te(){return v(function(){let e=module.require;return h(e)},arguments)}function Ee(){return v(function(e,t){s(e).randomFillSync(l(t))},arguments)}function De(){return v(function(e,t){s(e).getRandomValues(s(t))},arguments)}function Oe(e,t){let n=s(e)[t>>>0];return h(n)}function ke(e){return s(e).length}function Ae(e,t){return h(Function(m(e,t)))}function je(e){let t=s(e).next;return h(t)}function Me(){return v(function(e){return h(s(e).next())},arguments)}function Ne(e){return s(e).done}function Pe(e){let t=s(e).value;return h(t)}function Fe(){let e=Symbol.iterator;return h(e)}function Ie(){return v(function(e,t){return h(Reflect.get(s(e),s(t)))},arguments)}function Le(){return v(function(e,t){return h(s(e).call(s(t)))},arguments)}function Re(){return h({})}function ze(){return v(function(){let e=self.self;return h(e)},arguments)}function Be(){return v(function(){let e=window.window;return h(e)},arguments)}function Ve(){return v(function(){let e=globalThis.globalThis;return h(e)},arguments)}function He(){return v(function(){let e=global.global;return h(e)},arguments)}function Ue(e){return h(Array.from(s(e)))}function We(e){return Array.isArray(s(e))}function Ge(e){let t;try{t=s(e)instanceof ArrayBuffer}catch{t=!1}return t}function Ke(){return v(function(e,t,n){return h(s(e).call(s(t),s(n)))},arguments)}function qe(e){return Number.isSafeInteger(s(e))}function Je(e){return h(Object.entries(s(e)))}function Ye(e,t){try{var n={a:e,b:t};return h(new Promise((e,t)=>{let r=n.a;n.a=0;try{return ae(r,n.b,e,t)}finally{n.a=r}}))}finally{n.a=n.b=0}}function Xe(e){return h(Promise.resolve(s(e)))}function Ze(e,t){return h(s(e).then(s(t)))}function Qe(e,t,n){return h(s(e).then(s(t),s(n)))}function $e(e){let t=s(e).buffer;return h(t)}function et(e,t,n){return h(new Uint8Array(s(e),t>>>0,n>>>0))}function tt(e){return h(new Uint8Array(s(e)))}function nt(e,t,n){s(e).set(s(t),n>>>0)}function rt(e){return s(e).length}function it(e){let t;try{t=s(e)instanceof Uint8Array}catch{t=!1}return t}function at(e){return h(new Uint8Array(e>>>0))}function ot(e,t,n){return h(s(e).subarray(t>>>0,n>>>0))}function st(){return v(function(e){return h(JSON.stringify(s(e)))},arguments)}function ct(){return v(function(e,t,n){return Reflect.set(s(e),s(t),s(n))},arguments)}function lt(e,t){let n=d(g(s(t)),V.__wbindgen_malloc,V.__wbindgen_realloc),r=W;p()[e/4+1]=r,p()[e/4+0]=n}function ut(e,t){throw Error(m(e,t))}function dt(){let e=V.memory;return h(e)}function ft(e,t,n){return h(te(e,t,123,ne))}function pt(e,t,n){return h(te(e,t,217,re))}var V,H,U,W,G,K,mt,q,J,Y,ht,gt,_t,vt,yt,X,Z,Q=e((()=>{H=Array(128).fill(void 0),H.push(void 0,null,!0,!1),U=H.length,W=0,G=null,K=new(typeof TextEncoder>`u`?(0,module.require)(`util`).TextEncoder:TextEncoder)(`utf-8`),mt=typeof K.encodeInto==`function`?function(e,t){return K.encodeInto(e,t)}:function(e,t){let n=K.encode(e);return t.set(n),{read:e.length,written:n.length}},q=null,J=new(typeof TextDecoder>`u`?(0,module.require)(`util`).TextDecoder:TextDecoder)(`utf-8`,{ignoreBOM:!0,fatal:!0}),J.decode(),Y=null,ht=Object.freeze({InvalidAuth:0,0:`InvalidAuth`,UpgradeRequired:1,1:`UpgradeRequired`,RateLimitExceeded:2,2:`RateLimitExceeded`,Assertion:3,3:`Assertion`,Transient:4,4:`Transient`}),gt=Object.freeze({InvalidAuth:0,0:`InvalidAuth`,UpgradeRequired:1,1:`UpgradeRequired`,RateLimitExceeded:2,2:`RateLimitExceeded`,Assertion:3,3:`Assertion`,Transient:4,4:`Transient`}),_t=Object.freeze({InvalidPin:0,0:`InvalidPin`,NotRegistered:1,1:`NotRegistered`,InvalidAuth:2,2:`InvalidAuth`,UpgradeRequired:3,3:`UpgradeRequired`,RateLimitExceeded:4,4:`RateLimitExceeded`,Assertion:5,5:`Assertion`,Transient:6,6:`Transient`}),vt=class{__destroy_into_raw(){let e=this.__wbg_ptr;return this.__wbg_ptr=0,e}free(){let e=this.__destroy_into_raw();V.__wbg_authtokengenerator_free(e)}constructor(e){let t=V.authtokengenerator_new(h(e));return this.__wbg_ptr=t>>>0,this}vend(e,t){let n,r;try{let o=V.__wbindgen_add_to_stack_pointer(-16),s=d(e,V.__wbindgen_malloc,V.__wbindgen_realloc),c=W,l=d(t,V.__wbindgen_malloc,V.__wbindgen_realloc),u=W;V.authtokengenerator_vend(o,this.__wbg_ptr,s,c,l,u);var i=p()[o/4+0],a=p()[o/4+1];return n=i,r=a,m(i,a)}finally{V.__wbindgen_add_to_stack_pointer(16),V.__wbindgen_free(n,r,1)}}static random_secret_id(){let e,t;try{let i=V.__wbindgen_add_to_stack_pointer(-16);V.authtokengenerator_random_secret_id(i);var n=p()[i/4+0],r=p()[i/4+1];return e=n,t=r,m(n,r)}finally{V.__wbindgen_add_to_stack_pointer(16),V.__wbindgen_free(e,t,1)}}},yt=class{__destroy_into_raw(){let e=this.__wbg_ptr;return this.__wbg_ptr=0,e}free(){let e=this.__destroy_into_raw();V.__wbg_client_free(e)}constructor(e,t){ie(e,X);var n=e.__destroy_into_raw();let r=V.client_new(n,h(t));return this.__wbg_ptr=r>>>0,this}register(e,t,n,r){let i=_(e,V.__wbindgen_malloc),a=W,o=_(t,V.__wbindgen_malloc),s=W,c=_(n,V.__wbindgen_malloc),u=W;return l(V.client_register(this.__wbg_ptr,i,a,o,s,c,u,r))}recover(e,t){let n=_(e,V.__wbindgen_malloc),r=W,i=_(t,V.__wbindgen_malloc),a=W;return l(V.client_recover(this.__wbg_ptr,n,r,i,a))}delete(){return l(V.client_delete(this.__wbg_ptr))}},X=class{__destroy_into_raw(){let e=this.__wbg_ptr;return this.__wbg_ptr=0,e}free(){let e=this.__destroy_into_raw();V.__wbg_configuration_free(e)}constructor(e){let t=V.configuration_new(h(e));return this.__wbg_ptr=t>>>0,this}},Z=class e{static __wrap(t){t>>>=0;let n=Object.create(e.prototype);return n.__wbg_ptr=t,n}__destroy_into_raw(){let e=this.__wbg_ptr;return this.__wbg_ptr=0,e}free(){let e=this.__destroy_into_raw();V.__wbg_recovererror_free(e)}get reason(){return V.__wbg_get_recovererror_reason(this.__wbg_ptr)}set reason(e){V.__wbg_set_recovererror_reason(this.__wbg_ptr,e)}get guesses_remaining(){let e=V.__wbg_get_recovererror_guesses_remaining(this.__wbg_ptr);return e===16777215?void 0:e}set guesses_remaining(e){V.__wbg_set_recovererror_guesses_remaining(this.__wbg_ptr,f(e)?16777215:e)}}})),bt=t({__wbg_authtokengenerator_free:()=>Pt,__wbg_client_free:()=>At,__wbg_configuration_free:()=>Ct,__wbg_get_recovererror_guesses_remaining:()=>Dt,__wbg_get_recovererror_reason:()=>Tt,__wbg_recovererror_free:()=>wt,__wbg_set_recovererror_guesses_remaining:()=>Ot,__wbg_set_recovererror_reason:()=>Et,__wbindgen_add_to_stack_pointer:()=>Wt,__wbindgen_exn_store:()=>Ut,__wbindgen_export_2:()=>Bt,__wbindgen_free:()=>Gt,__wbindgen_malloc:()=>Rt,__wbindgen_realloc:()=>zt,_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h664375554e075ae3:()=>Ht,authtokengenerator_new:()=>Ft,authtokengenerator_random_secret_id:()=>Lt,authtokengenerator_vend:()=>It,client_delete:()=>$,client_new:()=>jt,client_recover:()=>Nt,client_register:()=>Mt,configuration_new:()=>kt,memory:()=>St,wasm_bindgen__convert__closures__invoke0_mut__hcf60f8fe499c96ac:()=>Vt,wasm_bindgen__convert__closures__invoke2_mut__h35a3737c9c8313d7:()=>Kt}),xt,St,Ct,wt,Tt,Et,Dt,Ot,kt,At,jt,Mt,Nt,$,Pt,Ft,It,Lt,Rt,zt,Bt,Vt,Ht,Ut,Wt,Gt,Kt,qt=e((async()=>{r(),a(),Q(),URL=globalThis.URL,xt=await i({"./juicebox-sdk_bg.js":{__wbindgen_object_drop_ref:oe,__wbg_recovererror_new:se,__wbindgen_cb_drop:ce,__wbindgen_string_get:le,__wbindgen_is_string:ue,__wbindgen_is_object:de,__wbindgen_is_undefined:fe,__wbindgen_in:pe,__wbindgen_error_new:y,__wbg_fetch_d70cd3e887817ac6:b,__wbg_setTimeout_deb7786d942a7ab5:x,__wbg_JuiceboxGetAuthToken_c2d75c5bf1346f44:S,__wbg_new_abda76e883ba8a5f:C,__wbg_stack_658279fe44541cf6:w,__wbg_error_f851667af71bcfc6:T,__wbg_queueMicrotask_4d890031a6a5a50c:E,__wbg_queueMicrotask_adae4bc085237231:D,__wbindgen_is_function:O,__wbindgen_object_clone_ref:k,__wbindgen_jsval_loose_eq:A,__wbindgen_boolean_get:j,__wbindgen_number_get:M,__wbindgen_as_number:N,__wbindgen_number_new:P,__wbindgen_string_new:F,__wbg_getwithrefkey_4a92a5eca60879b9:I,__wbg_now_0343d9c3e0e8eedc:L,__wbg_now_b724952e890dc703:R,__wbg_headers_d135d2bb8cc60413:z,__wbg_newwithstrandinit_f581dff0d19a8b03:B,__wbg_set_27f236f6d7a28c29:me,__wbg_arrayBuffer_a9d862b05aaee2f9:he,__wbg_instanceof_Response_4c3b1446206114d1:ge,__wbg_status_d6d47ad2837621eb:_e,__wbg_headers_24def508a7518df9:ve,__wbg_blob_c6537f3e31e66dad:ye,__wbg_crypto_58f13aa23ffcb166:be,__wbg_process_5b786e71d465a513:xe,__wbg_versions_c2ab80650590b6a2:Se,__wbg_node_523d7bd03ef69fba:Ce,__wbg_msCrypto_abcb1295e768d1f2:we,__wbg_require_2784e593a4674877:Te,__wbg_randomFillSync_a0d98aa11c81fe89:Ee,__wbg_getRandomValues_504510b5564925af:De,__wbg_get_f01601b5a68d10e3:Oe,__wbg_length_1009b1af0c481d7b:ke,__wbg_newnoargs_c62ea9419c21fbac:Ae,__wbg_next_9b877f231f476d01:je,__wbg_next_6529ee0cca8d57ed:Me,__wbg_done_5fe336b092d60cf2:Ne,__wbg_value_0c248a78fdc8e19f:Pe,__wbg_iterator_db7ca081358d4fb2:Fe,__wbg_get_7b48513de5dc5ea4:Ie,__wbg_call_90c26b09837aba1c:Le,__wbg_new_9fb8d994e1c0aaac:Re,__wbg_self_f0e34d89f33b99fd:ze,__wbg_window_d3b084224f4774d7:Be,__wbg_globalThis_9caa27ff917c6860:Ve,__wbg_global_35dfdd59a4da3e74:He,__wbg_from_71add2e723d1f1b2:Ue,__wbg_isArray_74fb723e24f76012:We,__wbg_instanceof_ArrayBuffer_e7d53d51371448e2:Ge,__wbg_call_5da1969d7cd31ccd:Ke,__wbg_isSafeInteger_f93fde0dca9820f8:qe,__wbg_entries_9e2e2aa45aa5094a:Je,__wbg_new_60f57089c7563e81:Ye,__wbg_resolve_6e1c6553a82f85b7:Xe,__wbg_then_3ab08cd4fbb91ae9:Ze,__wbg_then_8371cc12cfedc5a2:Qe,__wbg_buffer_a448f833075b71ba:$e,__wbg_newwithbyteoffsetandlength_d0482f893617af71:et,__wbg_new_8f67e318f15d7254:tt,__wbg_set_2357bf09366ee480:nt,__wbg_length_1d25fa9e4ac21ce7:rt,__wbg_instanceof_Uint8Array_bced6f43aed8c1aa:it,__wbg_newwithlength_6c2df9e2f3028c43:at,__wbg_subarray_2e940e41c0f5a1d9:ot,__wbg_stringify_e1b19966d964d242:st,__wbg_set_759f75cd92b612d2:ct,__wbindgen_debug_string:lt,__wbindgen_throw:ut,__wbindgen_memory:dt,__wbindgen_closure_wrapper610:ft,__wbindgen_closure_wrapper898:pt}},n),{memory:St,__wbg_configuration_free:Ct,__wbg_recovererror_free:wt,__wbg_get_recovererror_reason:Tt,__wbg_set_recovererror_reason:Et,__wbg_get_recovererror_guesses_remaining:Dt,__wbg_set_recovererror_guesses_remaining:Ot,configuration_new:kt,__wbg_client_free:At,client_new:jt,client_register:Mt,client_recover:Nt,client_delete:$,__wbg_authtokengenerator_free:Pt,authtokengenerator_new:Ft,authtokengenerator_vend:It,authtokengenerator_random_secret_id:Lt,__wbindgen_malloc:Rt,__wbindgen_realloc:zt,__wbindgen_export_2:Bt,wasm_bindgen__convert__closures__invoke0_mut__hcf60f8fe499c96ac:Vt,_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h664375554e075ae3:Ht,__wbindgen_exn_store:Ut,__wbindgen_add_to_stack_pointer:Wt,__wbindgen_free:Gt,wasm_bindgen__convert__closures__invoke2_mut__h35a3737c9c8313d7:Kt}=xt}));await e((async()=>{await qt(),Q(),Q(),o(bt)}))();export{vt as AuthTokenGenerator,yt as Client,X as Configuration,ht as DeleteError,Z as RecoverError,_t as RecoverErrorReason,gt as RegisterError,S as __wbg_JuiceboxGetAuthToken_c2d75c5bf1346f44,he as __wbg_arrayBuffer_a9d862b05aaee2f9,ye as __wbg_blob_c6537f3e31e66dad,$e as __wbg_buffer_a448f833075b71ba,Ke as __wbg_call_5da1969d7cd31ccd,Le as __wbg_call_90c26b09837aba1c,be as __wbg_crypto_58f13aa23ffcb166,Ne as __wbg_done_5fe336b092d60cf2,Je as __wbg_entries_9e2e2aa45aa5094a,T as __wbg_error_f851667af71bcfc6,b as __wbg_fetch_d70cd3e887817ac6,Ue as __wbg_from_71add2e723d1f1b2,De as __wbg_getRandomValues_504510b5564925af,Ie as __wbg_get_7b48513de5dc5ea4,Oe as __wbg_get_f01601b5a68d10e3,I as __wbg_getwithrefkey_4a92a5eca60879b9,Ve as __wbg_globalThis_9caa27ff917c6860,He as __wbg_global_35dfdd59a4da3e74,ve as __wbg_headers_24def508a7518df9,z as __wbg_headers_d135d2bb8cc60413,Ge as __wbg_instanceof_ArrayBuffer_e7d53d51371448e2,ge as __wbg_instanceof_Response_4c3b1446206114d1,it as __wbg_instanceof_Uint8Array_bced6f43aed8c1aa,We as __wbg_isArray_74fb723e24f76012,qe as __wbg_isSafeInteger_f93fde0dca9820f8,Fe as __wbg_iterator_db7ca081358d4fb2,ke as __wbg_length_1009b1af0c481d7b,rt as __wbg_length_1d25fa9e4ac21ce7,we as __wbg_msCrypto_abcb1295e768d1f2,Ye as __wbg_new_60f57089c7563e81,tt as __wbg_new_8f67e318f15d7254,Re as __wbg_new_9fb8d994e1c0aaac,C as __wbg_new_abda76e883ba8a5f,Ae as __wbg_newnoargs_c62ea9419c21fbac,et as __wbg_newwithbyteoffsetandlength_d0482f893617af71,at as __wbg_newwithlength_6c2df9e2f3028c43,B as __wbg_newwithstrandinit_f581dff0d19a8b03,Me as __wbg_next_6529ee0cca8d57ed,je as __wbg_next_9b877f231f476d01,Ce as __wbg_node_523d7bd03ef69fba,L as __wbg_now_0343d9c3e0e8eedc,R as __wbg_now_b724952e890dc703,xe as __wbg_process_5b786e71d465a513,E as __wbg_queueMicrotask_4d890031a6a5a50c,D as __wbg_queueMicrotask_adae4bc085237231,Ee as __wbg_randomFillSync_a0d98aa11c81fe89,se as __wbg_recovererror_new,Te as __wbg_require_2784e593a4674877,Xe as __wbg_resolve_6e1c6553a82f85b7,ze as __wbg_self_f0e34d89f33b99fd,x as __wbg_setTimeout_deb7786d942a7ab5,nt as __wbg_set_2357bf09366ee480,me as __wbg_set_27f236f6d7a28c29,ct as __wbg_set_759f75cd92b612d2,o as __wbg_set_wasm,w as __wbg_stack_658279fe44541cf6,_e as __wbg_status_d6d47ad2837621eb,st as __wbg_stringify_e1b19966d964d242,ot as __wbg_subarray_2e940e41c0f5a1d9,Ze as __wbg_then_3ab08cd4fbb91ae9,Qe as __wbg_then_8371cc12cfedc5a2,Pe as __wbg_value_0c248a78fdc8e19f,Se as __wbg_versions_c2ab80650590b6a2,Be as __wbg_window_d3b084224f4774d7,N as __wbindgen_as_number,j as __wbindgen_boolean_get,ce as __wbindgen_cb_drop,ft as __wbindgen_closure_wrapper610,pt as __wbindgen_closure_wrapper898,lt as __wbindgen_debug_string,y as __wbindgen_error_new,pe as __wbindgen_in,O as __wbindgen_is_function,de as __wbindgen_is_object,ue as __wbindgen_is_string,fe as __wbindgen_is_undefined,A as __wbindgen_jsval_loose_eq,dt as __wbindgen_memory,M as __wbindgen_number_get,P as __wbindgen_number_new,k as __wbindgen_object_clone_ref,oe as __wbindgen_object_drop_ref,le as __wbindgen_string_get,F as __wbindgen_string_new,ut as __wbindgen_throw};
@@ -1,3 +1,5 @@
1
+ import parseTimeline from "../parsers/timeline.js";
2
+
1
3
  export async function create(name, opts = {}) {
2
4
  return await this.graphql("CreateList", {
3
5
  body: {
@@ -54,7 +56,7 @@ export async function removeMember(listId, userId) {
54
56
  }
55
57
 
56
58
  export async function members(listId, opts = {}) {
57
- return await this.graphql("ListMembers", {
59
+ const raw = await this.graphql("ListMembers", {
58
60
  variables: {
59
61
  listId,
60
62
  count: opts.count || 20,
@@ -62,10 +64,11 @@ export async function members(listId, opts = {}) {
62
64
  ...opts.variables,
63
65
  },
64
66
  });
67
+ return parseTimeline(raw);
65
68
  }
66
69
 
67
70
  export async function subscribers(listId, opts = {}) {
68
- return await this.graphql("ListSubscribers", {
71
+ const raw = await this.graphql("ListSubscribers", {
69
72
  variables: {
70
73
  listId,
71
74
  count: opts.count || 20,
@@ -73,6 +76,7 @@ export async function subscribers(listId, opts = {}) {
73
76
  ...opts.variables,
74
77
  },
75
78
  });
79
+ return parseTimeline(raw);
76
80
  }
77
81
 
78
82
  export async function subscribe(listId) {
@@ -1,4 +1,5 @@
1
1
  import getCycleTLS from "../cycletls.js";
2
+ import parseTimeline from "../parsers/timeline.js";
2
3
  import parseTweet from "../parsers/tweet.js";
3
4
 
4
5
  async function createPollCard(instance, poll) {
@@ -262,7 +263,7 @@ export async function getMany(tweetIds) {
262
263
  }
263
264
 
264
265
  export async function detail(tweetId, opts = {}) {
265
- return await this.graphql("TweetDetail", {
266
+ const raw = await this.graphql("TweetDetail", {
266
267
  variables: {
267
268
  focalTweetId: tweetId,
268
269
  with_rux_injections: false,
@@ -280,6 +281,7 @@ export async function detail(tweetId, opts = {}) {
280
281
  withAuxiliaryUserLabels: false,
281
282
  },
282
283
  });
284
+ return parseTimeline(raw);
283
285
  }
284
286
 
285
287
  export async function editHistory(tweetId) {
@@ -0,0 +1,127 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ const FFMPEG = process.env.FFMPEG_PATH || "ffmpeg";
4
+
5
+ function decodeAll(args) {
6
+ return new Promise((resolve, reject) => {
7
+ const ff = spawn(FFMPEG, ["-hide_banner", "-loglevel", "error", ...args]);
8
+ const chunks = [];
9
+ const err = [];
10
+ ff.stdout.on("data", (d) => chunks.push(d));
11
+ ff.stderr.on("data", (d) => err.push(d));
12
+ ff.on("error", reject);
13
+ ff.on("close", (code) => {
14
+ if (code === 0) return resolve(Buffer.concat(chunks));
15
+ reject(new Error(`ffmpeg exited ${code}: ${Buffer.concat(err).toString().slice(0, 400)}`));
16
+ });
17
+ });
18
+ }
19
+
20
+ // Decode any audio file to raw PCM and push it into an RTCAudioSource at 10ms cadence.
21
+ // Returns { done: Promise, stop() }.
22
+ export async function streamAudioFile(source, filePath, opts = {}) {
23
+ const sampleRate = opts.sampleRate ?? 48000;
24
+ const channels = opts.channels ?? 1;
25
+ const samplesPerFrame = (sampleRate / 100) * channels; // 10ms
26
+ const bytesPerFrame = samplesPerFrame * 2;
27
+ const pcm = await decodeAll(["-i", filePath, "-f", "s16le", "-acodec", "pcm_s16le", "-ac", String(channels), "-ar", String(sampleRate), "pipe:1"]);
28
+
29
+ let offset = 0;
30
+ let timer = null;
31
+ let resolveDone;
32
+ const done = new Promise((r) => (resolveDone = r));
33
+ const tick = () => {
34
+ if (offset >= pcm.length) {
35
+ clearInterval(timer);
36
+ resolveDone({ frames: Math.ceil(pcm.length / bytesPerFrame) });
37
+ return;
38
+ }
39
+ const samples = new Int16Array(samplesPerFrame); // own 960-byte buffer, no aliasing
40
+ Buffer.from(samples.buffer).set(pcm.subarray(offset, Math.min(offset + bytesPerFrame, pcm.length)));
41
+ offset += bytesPerFrame;
42
+ source.onData({ samples, sampleRate, bitsPerSample: 16, channelCount: channels, numberOfFrames: sampleRate / 100 });
43
+ };
44
+ timer = setInterval(tick, 10);
45
+ return { done, stop: () => { if (timer) clearInterval(timer); resolveDone?.({ stopped: true }); } };
46
+ }
47
+
48
+ // Decode any video file to raw I420 frames and push them into an RTCVideoSource at the given fps.
49
+ export async function streamVideoFile(source, filePath, opts = {}) {
50
+ const width = opts.width ?? 320;
51
+ const height = opts.height ?? 240;
52
+ const fps = opts.fps ?? 15;
53
+ const frameBytes = (width * height * 3) / 2;
54
+ const raw = await decodeAll(["-i", filePath, "-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", `${width}x${height}`, "-r", String(fps), "pipe:1"]);
55
+
56
+ let offset = 0;
57
+ let timer = null;
58
+ let resolveDone;
59
+ const done = new Promise((r) => (resolveDone = r));
60
+ const tick = () => {
61
+ if (offset + frameBytes > raw.length) {
62
+ clearInterval(timer);
63
+ resolveDone({ frames: Math.floor(raw.length / frameBytes) });
64
+ return;
65
+ }
66
+ const data = new Uint8ClampedArray(raw.subarray(offset, offset + frameBytes));
67
+ offset += frameBytes;
68
+ source.onFrame({ width, height, data });
69
+ };
70
+ timer = setInterval(tick, Math.round(1000 / fps));
71
+ return { done, stop: () => { if (timer) clearInterval(timer); resolveDone?.({ stopped: true }); } };
72
+ }
73
+
74
+ // Pipe a received RTCAudioSink's PCM frames into a file (wav by default). Returns { stop() }.
75
+ export function recordAudioSink(sink, filePath, opts = {}) {
76
+ let ff = null;
77
+ let sampleRate = opts.sampleRate ?? 48000;
78
+ let channels = opts.channels ?? 1;
79
+ let frames = 0;
80
+ const start = (sr, ch) => {
81
+ sampleRate = sr;
82
+ channels = ch;
83
+ ff = spawn(FFMPEG, ["-hide_banner", "-loglevel", "error", "-y", "-f", "s16le", "-ar", String(sr), "-ac", String(ch), "-i", "pipe:0", filePath]);
84
+ };
85
+ sink.ondata = (d) => {
86
+ if (!ff) start(d.sampleRate, d.channelCount);
87
+ frames++;
88
+ ff.stdin.write(Buffer.from(d.samples.buffer, d.samples.byteOffset, d.samples.byteLength));
89
+ };
90
+ return {
91
+ get frames() { return frames; },
92
+ stop: () => new Promise((res) => {
93
+ sink.stop?.();
94
+ if (!ff) return res({ frames: 0 });
95
+ ff.stdin.end();
96
+ ff.on("close", () => res({ frames, sampleRate, channels }));
97
+ }),
98
+ };
99
+ }
100
+
101
+ // Pipe a received RTCVideoSink's I420 frames into a file (mp4 by default). Returns { stop() }.
102
+ export function recordVideoSink(sink, filePath, opts = {}) {
103
+ let ff = null;
104
+ let fps = opts.fps ?? 15;
105
+ let frames = 0;
106
+ let dims = null;
107
+ const start = (w, h) => {
108
+ dims = { w, h };
109
+ ff = spawn(FFMPEG, ["-hide_banner", "-loglevel", "error", "-y", "-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", `${w}x${h}`, "-r", String(fps), "-i", "pipe:0", "-pix_fmt", "yuv420p", filePath]);
110
+ };
111
+ sink.onframe = ({ frame }) => {
112
+ if (!ff) start(frame.width, frame.height);
113
+ if (frame.width !== dims.w || frame.height !== dims.h) return;
114
+ frames++;
115
+ ff.stdin.write(Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength));
116
+ };
117
+ return {
118
+ get frames() { return frames; },
119
+ get dims() { return dims; },
120
+ stop: () => new Promise((res) => {
121
+ sink.stop?.();
122
+ if (!ff) return res({ frames: 0 });
123
+ ff.stdin.end();
124
+ ff.on("close", () => res({ frames, dims }));
125
+ }),
126
+ };
127
+ }