fingerprintjs
109 строк · 2.9 Кб
1import * as fs from 'fs'2import * as readline from 'readline'3import { spawn, SpawnOptions } from 'child_process'4import { URL } from 'url'5import got from 'got'6
7export async function eachLineInFile(8filePath: string,9callback: (line: string) => void | Promise<void>,10): Promise<void> {11const fileStream = fs.createReadStream(filePath)12
13try {14const reader = readline.createInterface({15input: fileStream,16crlfDelay: Infinity,17})18const iterator = reader[Symbol.asyncIterator]()19
20for (;;) {21const value = await iterator.next()22if (value.done) {23break24}25
26await callback(value.value)27}28} finally {29fileStream.destroy()30}31}
32
33export async function fetchFilter(34url: string,35abort?: Promise<unknown>,36forceTreatAsFilter = false,37): Promise<string[]> {38const request = got(url, {39headers: {40'User-Agent':41'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ' +42'Chrome/91.0.4472.114 Safari/537.36',43},44})45
46abort?.catch(() => undefined).then(() => request.cancel())47
48const response = await request.catch((error) => {49if (request.isCanceled) {50// Never resolve the promise of the function when it's aborted51return new Promise<never>(() => undefined)52}53throw error54})55
56if (response.statusCode >= 300) {57throw new Error(`HTTP status ${response.statusCode} (${response.statusMessage})`)58}59
60if (61!forceTreatAsFilter &&62!/^( *\[Adblock Plus.*] *(\r\n|\r|\n))?( *! *[a-z].*(\r\n|\r|\n)){2}/im.test(response.body)63) {64throw new Error("The response doesn't look like a filter")65}66
67const filterLines = response.body.split(/(\r\n|\r|\n)/) // AdGuard filters use \r sometimes68const subFilterURLs: string[] = []69
70for (const line of filterLines) {71const match = /^!#include +(.*?) *$/.exec(line)72if (match) {73// See https://stackoverflow.com/a/45801884/111870974const subFilterURL = new URL(match[1], url).href75subFilterURLs.push(subFilterURL)76}77}78
79await Promise.all(80subFilterURLs.map(async (subFilterURL) => {81let subFilterLines: string[]82try {83// We expect no recursion in actual filters84subFilterLines = await fetchFilter(subFilterURL, abort, true)85} catch (error) {86throw new Error(`Failed to fetch a sub-filter (${subFilterURL}): ${error}`)87}88filterLines.push(...subFilterLines)89}),90)91
92return filterLines93}
94
95export function runCommand(command: string, options: SpawnOptions = {}): Promise<void> {96return new Promise<void>((resolve, reject) => {97const child = spawn(command, [], { shell: true, ...options })98child.stdout?.pipe(process.stdout)99child.stderr?.pipe(process.stderr)100child.on('error', reject)101child.on('close', (code) => {102if (code) {103reject(new Error(`The ${command} command has exited with code ${code}`))104} else {105resolve()106}107})108})109}
110