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
19 changes: 19 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div align="center">
<img src="./assets/qnaplus.png">
<h1>scraper</h1>
<p>Scraping utilities for the <a href="https://events.vex.com/V5RC/2026-2027/QA/">VEX Robotics Q&A</a>.
</p>
<a href="https://github.com/qnaplus/scraper/stargazers">
<img src="https://img.shields.io/github/stars/qnaplus/scraper?color=577BB5&labelColor=1A1B26&style=for-the-badge">
</a>
<a href="../LICENSE.md">
<img src="https://img.shields.io/github/license/qnaplus/scraper?color=C0CAF5&labelColor=1A1B26&style=for-the-badge">
</a>
</div>

## Packages

| Package | Description |
| --- | --- |
| [@qnaplus/scraper](../packages/scraper) | Core utilities for scraping and querying VEX Q&A questions |
| [@qnaplus/scraper-strategies](../packages/scraper-strategies) | Request strategies for `@qnaplus/scraper` |
Binary file added .github/assets/qnaplus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 4 additions & 5 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: echo "PACKAGE_JSON=$(jq -c . < package.json)" >> $GITHUB_ENV
- uses: pnpm/action-setup@v4
with:
version: ${{ fromJson(env.PACKAGE_JSON).volta.pnpm }}
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ fromJson(env.PACKAGE_JSON).volta.node }}
registry-url: https://registry.npmjs.org/
cach: pnpm
- name: Upgrade npm for OCID support
run: npm i -g npm@latest
- run: pnpm install --frozen-lockfile
Expand Down
25 changes: 19 additions & 6 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@ jobs:
neat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v4
- uses: actions/checkout@v6
- run: echo "PACKAGE_JSON=$(jq -c . < package.json)" >> $GITHUB_ENV
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
version: 10.7.0
- uses: actions/setup-node@v3
with:
node-version: 22.14.0
node-version: ${{ fromJson(env.PACKAGE_JSON).volta.node }}
registry-url: https://registry.npmjs.org/
cache: pnpm
- run: pnpm i --frozen-lockfile
- name: Check version bumps
run: |
for pkg in packages/scraper packages/scraper-strategies; do
name=$(jq -r .name "$pkg/package.json")
version=$(jq -r .version "$pkg/package.json")
if pnpm view "$name@$version" version &>/dev/null; then
echo "::error::$name@$version already exists on npm. Please bump the version."
exit 1
else
echo "$name@$version is not yet published."
fi
done
- run: pnpm neat
- run: pnpm test
9 changes: 9 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
"src/oc/build/**",
"src/vite-env.d.ts"
],
"tabWidth": 4,
"useTabs": true
}
16 changes: 16 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [
"typescript",
"unicorn",
"oxc",
"import"
],
"categories": {
"correctness": "error"
},
"rules": {},
"env": {
"builtin": true
}
}
30 changes: 0 additions & 30 deletions biome.json

This file was deleted.

20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@
"author": "battlesqui_d",
"license": "GPL-3.0",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"rimraf": "^6.0.1",
"typescript": "5.5.4"
"oxfmt": "^0.49.0",
"oxlint": "^1.64.0",
"typescript": "6.0.3"
},
"scripts": {
"build": "tsc --build",
"clean": "pnpm -r clean",
"reset": "pnpm -r reset && rimraf node_modules",
"reset": "pnpm -r reset && rm -rf node_modules",
"test": "pnpm -r test",
"neat": "biome check .",
"neat:fix": "biome check . --write"
"lint": "pnpm -r lint",
"lint:fix": "pnpm -r lint:fix",
"fmt": "pnpm -r fmt",
"fmt:fix": "pnpm -r fmt:fix",
"neat": "pnpm lint && pnpm fmt",
"neat:fix": "pnpm lint:fix && pnpm fmt:fix"
},
"volta": {
"pnpm": "10.7.0",
"pnpm": "11.1.2",
"node": "22.14.0"
},
"packageManager": "pnpm@10.7.0"
"packageManager": "pnpm@11.1.2"
}
25 changes: 15 additions & 10 deletions packages/scraper-strategies/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qnaplus/scraper-strategies",
"version": "3.1.2",
"version": "3.1.3",
"description": "Request stategies for @qnaplus/scraper",
"main": "dist/index.js",
"repository": {
Expand All @@ -11,23 +11,28 @@
"scripts": {
"build": "tsc",
"build:clean": "pnpm clean && pnpm build",
"clean": "rimraf dist tsconfig.tsbuildinfo",
"reset": "pnpm clean && rimraf node_modules"
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"reset": "pnpm clean && rm -rf node_modules",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"fmt": "oxfmt --check src",
"fmt:fix": "oxfmt src"
},
"dependencies": {
"@qnaplus/node-curl-impersonate": "^1.0.2",
"@qnaplus/scraper": "workspace:*",
"cycletls": "^1.0.27",
"form-data": "^4.0.1",
"header-generator": "^2.1.62",
"pino": "catalog:"
"cycletls": "^2.0.5",
"header-generator": "^2.1.82",
"pino": "catalog:",
"ts-curl-impersonate": "^1.0.9"
},
"devDependencies": {
"@qnaplus/typescript-config": "workspace:1.0.0",
"@qnaplus/typescript-config": "workspace:*",
"pino-pretty": "catalog:"
},
"volta": {
"extends": "../../package.json"
},
"files": ["dist"]
"files": [
"dist"
]
}
23 changes: 6 additions & 17 deletions packages/scraper-strategies/src/curl_impersonate_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
type RequestPreset,
type SafariPresetVersion,
getCompatibleBrowsers,
} from "@qnaplus/node-curl-impersonate";
} from "ts-curl-impersonate";
import { FetchClient, type FetchClientResponse } from "@qnaplus/scraper";

export class CurlImpersonateScrapingClient extends FetchClient<FetchClientResponse> {
Expand All @@ -18,10 +18,7 @@ export class CurlImpersonateScrapingClient extends FetchClient<FetchClientRespon
if (ok !== undefined) {
return;
}
const response = await new RequestBuilder()
.url(url)
.preset(preset)
.send();
const response = await new RequestBuilder().url(url).preset(preset).send();
if (response.stderr !== undefined) {
throw new Error(response.stderr); // TODO figure out how to handle this
}
Expand Down Expand Up @@ -64,23 +61,19 @@ export class CurlImpersonateScrapingClient extends FetchClient<FetchClientRespon
// TODO: when a bad status code is not recieved, cache the preset that was used.
// Continue using that preset until failure, then search again for another working preset.
if (badStatusCodes.includes(latestResponse.status)) {
this.logger?.trace(
`All presets failed (latest response: ${latestResponse}`,
);
this.logger?.trace(`All presets failed (latest response: ${latestResponse}`);
}
return latestResponse;
}

async buffer(): Promise<ArrayBufferLike | null> {
async buffer(): Promise<ArrayBuffer | null> {
// TODO: implement reading response buffer
return null;
}

teardown(): Promise<void> | void {}

private async forEachPreset(
cb: (preset: RequestPreset<BrowserType>) => void | Promise<void>,
) {
private async forEachPreset(cb: (preset: RequestPreset<BrowserType>) => void | Promise<void>) {
const browsers = getCompatibleBrowsers();
for (const browser of browsers) {
for (const version of Object.keys(BrowserPresets[browser.name])) {
Expand All @@ -99,11 +92,7 @@ export class CurlImpersonateScrapingClient extends FetchClient<FetchClientRespon
url: string,
preset: RequestPreset<T>,
): Promise<CurlResultOk> {
const response = await new RequestBuilder()
.url(url)
.preset(preset)
.follow()
.send();
const response = await new RequestBuilder().url(url).preset(preset).follow().send();
if (response.stderr !== undefined) {
throw new Error(response.stderr); // TODO figure out how to handle this
}
Expand Down
8 changes: 5 additions & 3 deletions packages/scraper-strategies/src/cycletls_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ export class CycleTLSScrapingClient extends FetchClient<CycleTLSResponse> {

async fetch(url: string): Promise<CycleTLSResponse> {
const response = await this.getResponse(url);
const body = await response.text();
const ok = response.status === 200;
return {
body: response.body as string,
ok: response.status === 200,
body,
ok,
url: response.finalUrl,
status: response.status,
};
}

async buffer(): Promise<ArrayBufferLike | null> {
async buffer(): Promise<ArrayBuffer | null> {
// TODO: implement reading response buffer
return null;
}
Expand Down
24 changes: 15 additions & 9 deletions packages/scraper/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qnaplus/scraper",
"version": "3.1.2",
"version": "3.1.3",
"description": "Utility package to scrape questions from the VEX Robotics Q&A.",
"main": "dist/index.js",
"repository": {
Expand All @@ -12,25 +12,31 @@
"build": "tsc",
"build:clean": "pnpm clean && pnpm build",
"test": "vitest --run",
"clean": "rimraf dist tsconfig.tsbuildinfo",
"reset": "pnpm clean && rimraf node_modules"
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"reset": "pnpm clean && rm -rf node_modules",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"fmt": "oxfmt --check src",
"fmt:fix": "oxfmt src"
},
"dependencies": {
"@crawlee/core": "^3.11.5",
"cheerio": "^1.0.0",
"got-scraping": "^4.0.6",
"@crawlee/core": "^3.16.0",
"cheerio": "^1.2.0",
"got-scraping": "^4.2.1",
"node-pdf-parser": "^1.0.7",
"pino": "catalog:"
},
"devDependencies": {
"@qnaplus/typescript-config": "workspace:1.0.0",
"@qnaplus/typescript-config": "workspace:*",
"pino-pretty": "catalog:",
"vitest": "^3.1.2"
"vitest": "^4.1.6"
},
"volta": {
"extends": "../../package.json"
},
"files": ["dist"],
"files": [
"dist"
],
"exports": {
".": {
"default": "./dist/index.js",
Expand Down
8 changes: 2 additions & 6 deletions packages/scraper/src/clients/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ import type { Logger } from "pino";
import type { FetcherOptions } from "../modules/fetchers";
import { GotScrapingClient } from "./got_client";

export const getPreferredClient = async (
logger?: Logger,
): Promise<GotScrapingClient> => {
export const getPreferredClient = async (logger?: Logger): Promise<GotScrapingClient> => {
return new GotScrapingClient(logger, true);
};

export const getDefaultFetcherOptions = async (
options?: FetcherOptions,
): Promise<
Required<Omit<FetcherOptions, "logger">> & Pick<FetcherOptions, "logger">
> => {
): Promise<Required<Omit<FetcherOptions, "logger">> & Pick<FetcherOptions, "logger">> => {
const client = options?.client ?? (await getPreferredClient(options?.logger));
const teardown = options?.teardown ?? true;
return { client, teardown, logger: options?.logger };
Expand Down
2 changes: 1 addition & 1 deletion packages/scraper/src/clients/fetch_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export abstract class FetchClient<FetchResponse extends FetchClientResponse> {
constructor(protected logger?: Logger) {}

abstract fetch(url: string): Promise<FetchResponse>;
abstract buffer(url: string): Promise<ArrayBufferLike | null>;
abstract buffer(url: string): Promise<ArrayBuffer | null>;
abstract teardown(): Promise<void> | void;

/**
Expand Down
Loading