Vilnius Transport MCP Server
- src
import { launch, Page } from "puppeteer-core"
import { findBrowser } from "./find-browser"
import { domFetchAndEvaluate } from "./dom"
type RealBrowserOptions = {
type: "real"
show?: boolean
browser?: string
proxy?: string
}
type FakeBrowserOptions = {
type: "fake"
proxy?: string
}
type Options = RealBrowserOptions | FakeBrowserOptions
export type BrowserMethods = {
close: () => Promise<void>
evaluateOnPage: <T extends any[], R>(
url: string,
fn: (window: Window, ...args: T) => R,
fnArgs: T,
) => Promise<R | null>
}
export const launchBrowser = async (options: Options) => {
if (options.type === "real") {
return launchRealBrowser(options)
}
return launchFakeBrowser(options)
}
const launchRealBrowser = async (
options: RealBrowserOptions,
): Promise<BrowserMethods> => {
const browser = findBrowser(options.browser)
const context = await launch({
executablePath: browser.executable,
headless: !options.show,
args: [
// "--enable-webgl",
// "--use-gl=swiftshader",
// "--enable-accelerated-2d-canvas",
"--disable-blink-features=AutomationControlled",
// "--disable-web-security",
options.proxy ? `--proxy-server=${options.proxy}` : null,
].filter((v) => v !== null),
ignoreDefaultArgs: ["--enable-automation"],
defaultViewport: {
width: 1280,
height: 720,
},
downloadBehavior: {
policy: "deny",
},
})
return {
close: async () => {
context.close()
},
evaluateOnPage: async (url, fn, fnArgs) => {
const page = await context.newPage()
try {
await interceptRequest(page)
await page.goto(url, {
waitUntil: "networkidle2",
})
const win = await page.evaluateHandle(() => window)
const result = await page.evaluate(fn, win, ...fnArgs)
await win.dispose()
await page.close()
return result
} catch (error) {
await page.close()
throw error
}
},
}
}
const launchFakeBrowser = async (
options: FakeBrowserOptions,
): Promise<BrowserMethods> => {
return {
close: async () => {},
evaluateOnPage: async (url, fn, fnArgs) => {
const result = await domFetchAndEvaluate(
url,
(window, ...args) => fn(window as any, ...args),
fnArgs,
{ proxy: options.proxy },
)
return result
},
}
}
async function interceptRequest(page: Page) {
await applyStealthScripts(page)
await page.setRequestInterception(true)
page.on("request", (request) => {
const resourceType = request.resourceType()
if (resourceType !== "document") {
return request.abort()
}
if (request.isNavigationRequest()) {
return request.continue()
}
return request.abort()
})
}
async function applyStealthScripts(page: Page) {
await page.setBypassCSP(true)
await page.setUserAgent(
`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/237.84.2.178 Safari/537.36`,
)
await page.evaluate(() => {
// Override the navigator.webdriver property
Object.defineProperty(navigator, "webdriver", {
get: () => undefined,
})
// Mock languages and plugins to mimic a real browser
Object.defineProperty(navigator, "languages", {
get: () => ["en-US", "en"],
})
Object.defineProperty(navigator, "plugins", {
get: () => [1, 2, 3, 4, 5],
})
// Redefine the headless property
Object.defineProperty(navigator, "headless", {
get: () => false,
})
// Override the permissions API
const originalQuery = window.navigator.permissions.query
window.navigator.permissions.query = (parameters) =>
parameters.name === "notifications"
? Promise.resolve({
state: Notification.permission,
} as PermissionStatus)
: originalQuery(parameters)
})
}