Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
- name: Lint
run: bun run lint

- name: Typecheck
run: bun run typecheck

- name: Test with coverage
run: bun test --coverage --coverage-reporter=lcov

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
RUN bun install --frozen-lockfile --production

FROM mcr.microsoft.com/playwright:v1.62.1-noble AS runner
ARG BUILD_VERSION=1.6.0-dev
ARG BUILD_VERSION=1.7.0-dev
ARG BUILD_REVISION=development
ARG BUILD_TIME=unknown
RUN apt-get update \
Expand Down
63 changes: 53 additions & 10 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"name": "typetype-token",
"version": "1.6.0",
"version": "1.7.0",
"private": true,
"license": "MIT",
"scripts": {
"start": "bun run src/index.ts",
"build": "bun build src/index.ts --outfile dist/index.js --target bun --external playwright",
"test": "bun test",
"typecheck": "bun run typecheck:src && bun run typecheck:tests",
"typecheck:src": "tsc -p tsconfig.json",
"typecheck:tests": "tsc -p tsconfig.tests.json",
"lint": "biome check src tests",
"format": "biome format --write src tests"
},
Expand All @@ -17,8 +20,9 @@
"youtubei.js": "^17.2.0"
},
"devDependencies": {
"@biomejs/biome": "^2.5.7",
"bun-types": "^1.3.14"
"@biomejs/biome": "^2.5.10",
"bun-types": "^1.3.14",
"typescript": "~7.0.2"
},
"trustedDependencies": [
"@biomejs/biome"
Expand Down
5 changes: 4 additions & 1 deletion src/botguard-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ export async function executeBotGuard(
}
config.EVENT_ID = args.eventId;
new Function(args.script)();
const g = globalThis as Record<string, Record<string, (...a: unknown[]) => unknown>>;
const g = globalThis as unknown as Record<
string,
Record<string, (...a: unknown[]) => unknown>
>;
const vm = g[args.name];
if (!vm?.a) throw new Error("BotGuard VM not found");

Expand Down
2 changes: 1 addition & 1 deletion src/innertube.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IntegrityTokenData } from "bgutils-js";
import type { IntegrityTokenData } from "bgutils-js/shared-types";
import { WEB_CLIENT_VERSION } from "./botguard-challenge.ts";
import { youtubeFetch } from "./youtube-fetch.ts";

Expand Down
4 changes: 2 additions & 2 deletions src/remote-login-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ function launchOptions(config: RemoteLoginConfig): Parameters<typeof chromium.la
return {
headless: config.headless,
args: launchArgs(config),
channel: config.browserChannel ?? undefined,
executablePath: config.browserExecutablePath ?? undefined,
...(config.browserChannel ? { channel: config.browserChannel } : {}),
...(config.browserExecutablePath ? { executablePath: config.browserExecutablePath } : {}),
};
}

Expand Down
5 changes: 3 additions & 2 deletions src/remote-login-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export async function applyRemoteLoginInput(
return "applied";
}
await page.mouse.move(message.x, message.y);
if (message.event === "down") await page.mouse.down({ button: message.button });
if (message.event === "up") await page.mouse.up({ button: message.button });
const button = message.button ? { button: message.button } : undefined;
if (message.event === "down") await page.mouse.down(button);
if (message.event === "up") await page.mouse.up(button);
return "applied";
}
45 changes: 44 additions & 1 deletion src/remote-login-session.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { RemoteLoginPage } from "./remote-login-browser.ts";
import { sendRemoteLoginCompletion } from "./remote-login-callback.ts";
import type { RemoteLoginConfig } from "./remote-login-config.ts";
import { applyRemoteLoginInput } from "./remote-login-input.ts";
import {
errorMessage,
parseRemoteLoginInput,
type RemoteLoginInput,
type RemoteLoginPhase,
statusMessage,
} from "./remote-login-messages.ts";
Expand Down Expand Up @@ -31,6 +33,8 @@ export class RemoteLoginSession {
private expiryTimer: ReturnType<typeof setTimeout>;
private frameTimer: ReturnType<typeof setTimeout> | null = null;
private loginTimer: ReturnType<typeof setTimeout> | null = null;
private readonly inputQueue: RemoteLoginInput[] = [];
private inputDrainRunning = false;
constructor(options: RemoteLoginSessionOptions) {
this.sessionId = options.sessionId;
this.userId = options.userId;
Expand Down Expand Up @@ -67,7 +71,7 @@ export class RemoteLoginSession {
if (this.closed || typeof raw !== "string") return;
const message = parseRemoteLoginInput(raw);
if (!message) return;
void this.applyInput(message);
this.enqueueInput(message);
}
disconnect(): void {
this.fail("WebSocket disconnected");
Expand All @@ -81,6 +85,42 @@ export class RemoteLoginSession {
if (!page) return;
if ((await applyRemoteLoginInput(page, message)) === "cancelled") this.cancel();
}
private enqueueInput(message: RemoteLoginInput): void {
if (message.type === "cancel") {
this.inputQueue.length = 0;
this.inputQueue.unshift(message);
} else if (message.type === "pointer" && message.event === "move") {
const last = this.inputQueue.at(-1);
if (last?.type === "pointer" && last.event === "move") {
this.inputQueue[this.inputQueue.length - 1] = message;
} else if (this.inputQueue.length < MAX_INPUT_QUEUE) {
this.inputQueue.push(message);
}
} else {
if (this.inputQueue.length >= MAX_INPUT_QUEUE) {
const moveIndex = this.inputQueue.findIndex(
(input) => input.type === "pointer" && input.event === "move",
);
if (moveIndex >= 0) this.inputQueue.splice(moveIndex, 1);
}
if (this.inputQueue.length < MAX_INPUT_QUEUE) this.inputQueue.push(message);
}
void this.drainInputQueue();
}
private async drainInputQueue(): Promise<void> {
if (this.inputDrainRunning) return;
this.inputDrainRunning = true;
try {
while (!this.closed) {
const message = this.inputQueue.shift();
if (!message) return;
await this.applyInput(message);
}
} finally {
this.inputDrainRunning = false;
if (!this.closed && this.inputQueue.length > 0) void this.drainInputQueue();
}
}
private scheduleLoginCheck(): void {
if (this.closed || this.captureStarted) return;
this.loginTimer = setTimeout(() => void this.checkLogin(), 1000);
Expand Down Expand Up @@ -156,6 +196,7 @@ export class RemoteLoginSession {
private finish(code: number, reason: string): void {
if (this.closed) return;
this.closed = true;
this.inputQueue.length = 0;
clearTimeout(this.expiryTimer);
if (this.frameTimer) clearTimeout(this.frameTimer);
if (this.loginTimer) clearTimeout(this.loginTimer);
Expand All @@ -164,3 +205,5 @@ export class RemoteLoginSession {
this.onDone(this.sessionId, this.userId);
}
}

const MAX_INPUT_QUEUE = 128;
5 changes: 4 additions & 1 deletion src/subtitle-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ async function subtitleContent(url: URL): Promise<Response> {
);
}
try {
return new Response(await fetchSubtitleContent(rawUrl), {
const content = await fetchSubtitleContent(rawUrl);
const body = new ArrayBuffer(content.byteLength);
new Uint8Array(body).set(content);
return new Response(body, {
headers: {
"cache-control": "private, max-age=300",
"content-type": "text/vtt; charset=utf-8",
Expand Down
2 changes: 1 addition & 1 deletion src/youtube-caption-tracks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function toRawCaptionTrack(track: CaptionTrackData, client: YoutubeSabrClient):
).toString(),
name: { simpleText: track.name.toString() },
languageCode: track.language_code,
kind: track.kind,
...(track.kind ? { kind: track.kind } : {}),
vssId: track.vss_id,
};
}
2 changes: 1 addition & 1 deletion src/youtube-innertube-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const sessions = new YoutubeInnertubeSessions<YoutubeInnertube>(async (client, v
const innertube = await Innertube.create({
cache: new UniversalCache(true),
client_type: client === "MWEB" ? ClientType.MWEB : ClientType.WEB,
fetch: youtubeFetch,
fetch: youtubeFetch as typeof fetch,
visitor_data: visitorData,
});
if (client === "MWEB") {
Expand Down
5 changes: 2 additions & 3 deletions src/youtube-player-evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { EvalResult } from "youtubei.js";
import { Platform } from "youtubei.js";
import { Platform, type Types } from "youtubei.js";

export function evaluateYoutubePlayerScript(output: string): EvalResult {
export function evaluateYoutubePlayerScript(output: string): Types.EvalResult {
return new Function(output)();
}

Expand Down
Loading