A collection of type-safe web scrapers as importable TypeScript functions.
Installation • Quick Start • API Reference • Error Handling • Contributing
@ptheus/tools provides ready-to-use scrapers for common data sources — GitHub, npm, Hacker News, live exchange rates, and more. Every function follows the same Result<T> pattern: no exceptions are thrown, ever.
| Scraper | Functions |
|---|---|
| GitHub | getGitHubRepository · getGitHubUser · getGitHubTrending |
| PyPI | getPypiPackage |
| npm Registry | getNpmPackage · searchNpmPackages |
| Hacker News | getHackerNewsFeed · getHackerNewsItem · getHackerNewsMaxItem |
| Exchange Rates | getExchangeRates · convertCurrency · getSupportedCurrencies |
| Crypto Price | getCryptoPrice · getCryptoMarkets · getCoinList |
| Wikipedia | getWikipediaSummary · searchWikipedia · getWikipediaArticle |
| Wikiquote | getWikiquotePage · searchWikiquote |
| IP Geolocation | getIpGeoLocation |
| Quotes | getRandomQuote · getQuoteOfTheDay · getRandomQuotes |
| Weather | getWeatherForecast · searchWeatherLocations |
| Dictionary | getWordDefinition · getAllWordDefinitions |
| News / RSS | getRssFeed · searchNews · getTopNews |
npm install @ptheus/toolsRequirements: Node.js ≥ 21
import { getGitHubRepository } from "@ptheus/tools";
const result = await getGitHubRepository("facebook", "react");
if (result.success) {
console.log(result.data.stars);
console.log(result.data.language);
} else {
console.error(result.error.code);
}GitHub
import { getGitHubRepository, getGitHubUser, getGitHubTrending } from "@ptheus/tools";
const repo = await getGitHubRepository("microsoft", "vscode");
const user = await getGitHubUser("torvalds");
const trending = await getGitHubTrending({ language: "typescript", since: "weekly" });npm
import { getNpmPackage, searchNpmPackages } from "@ptheus/tools";
const pkg = await getNpmPackage("lodash");
const scoped = await getNpmPackage("@tanstack/react-query");
const results = await searchNpmPackages("react state management", { limit: 5 });PyPi
import { getPypiPackage } from "@ptheus/tools";
const pkg = await getPypiPackage("requests");Hacker News
import { getHackerNewsFeed, getHackerNewsItem, getHackerNewsMaxItem } from "@ptheus/tools";
const feed = await getHackerNewsFeed("top", { limit: 20 });
const item = await getHackerNewsItem(8863);
const max = await getHackerNewsMaxItem();Exchange Rates
import { getExchangeRates, convertCurrency, getSupportedCurrencies } from "@ptheus/tools";
const rates = await getExchangeRates("USD");
const converted = await convertCurrency(100, "USD", "IDR");
const currencies = await getSupportedCurrencies();Crypto Price
import { getCryptoPrice, getCryptoMarkets, getCoinList } from "@ptheus/tools";
const bitcoin = await getCryptoPrice("bitcoin");
const top10 = await getCryptoMarkets({ limit: 10 });
const allCoins = await getCoinList();
// Supports custom currency (default: "usd")
const btcIdr = await getCryptoPrice("bitcoin", { currency: "idr" });Wikipedia
import { getWikipediaSummary, searchWikipedia, getWikipediaArticle } from "@ptheus/tools";
const summary = await getWikipediaSummary("TypeScript");
const results = await searchWikipedia("open source software", { limit: 5 });
const article = await getWikipediaArticle("Node.js");
// All three accept a language option (default: "en")
const idSummary = await getWikipediaSummary("Pemrograman komputer", { lang: "id" });Wikiquote
import { getWikiquotePage, searchWikiquote } from "@ptheus/tools";
const page = await getWikiquotePage("Albert Einstein");
const results = await searchWikiquote("science", { limit: 5 });
// Supports multiple languages
const dePage = await getWikiquotePage("Albert Einstein", { lang: "de" });IP Geolocation
import { getIpGeoLocation } from "@ptheus/tools";
const location = await getIpGeoLocation("8.8.8.8");
console.log(location.data.city, location.data.country);
// Omit the IP to look up the requester's own address
const self = await getIpGeoLocation();Quotes
import { getRandomQuote, getQuoteOfTheDay, getRandomQuotes } from "@ptheus/tools";
const quote = await getRandomQuote();
const today = await getQuoteOfTheDay();
const batch = await getRandomQuotes(); // ~50 quotes at onceForecast
import { getWeatherForecast, searchWeatherLocations } from "@ptheus/tools";
const forecast = await getWeatherForecast("Jakarta", { days: 5 });
console.log(forecast.data.current.temperatureC);
console.log(forecast.data.daily);
const matches = await searchWeatherLocations("Springfield");Dictionary
import { getWordDefinition, getAllWordDefinitions } from "@ptheus/tools";
const entry = await getWordDefinition("ubiquitous");
console.log(entry.data.meanings[0].definitions[0].definition);
// All entries (e.g. multiple parts of speech from different sources)
const entries = await getAllWordDefinitions("run");RSS Feed
import { getRssFeed, searchNews, getTopNews } from "@ptheus/tools";
const feed = await getRssFeed("https://hnrss.org/frontpage");
const results = await searchNews("artificial intelligence", { limit: 10 });
const headlines = await getTopNews({ country: "US", lang: "en" });interface ScraperOptions {
timeoutMs?: number; // default: 10000 (10 seconds)
userAgent?: string; // default: "@ptheus/tools scraper"
}
const result = await getGitHubRepository("owner", "repo", { timeoutMs: 5000 });All functions return a Result<T> discriminated union — exceptions are never thrown.
type Result<T> =
| { success: true; data: T }
| { success: false; error: ScraperError };
type ScraperErrorCode =
| "NETWORK_ERROR"
| "PARSE_ERROR"
| "VALIDATION_ERROR"
| "NOT_FOUND"
| "RATE_LIMITED"
| "UNKNOWN";const result = await getNpmPackage("some-package");
if (!result.success) {
switch (result.error.code) {
case "NOT_FOUND":
console.log("Package does not exist");
break;
case "NETWORK_ERROR":
console.log("Could not reach npm registry");
break;
case "RATE_LIMITED":
console.log("Too many requests — try again later");
break;
default:
console.error(result.error.message);
}
}@ptheus/tools
└── src/
├── index.ts
├── core/
│ ├── http.ts
│ └── result.ts
├── scrapers/
│ ├── encyclopedia/
│ │ ├── wikipedia/
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── wikipedia.test.ts
│ │ └── wikiquote/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── wikiquote.test.ts
│ ├── entertainment/
│ │ └── quotes/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── quotes.test.ts
│ ├── finance/
│ │ ├── exchange-rate/
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── exchange-rate.test.ts
│ │ └── crypto-price/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── crypto-price.test.ts
│ ├── network/
│ │ └── ip-geo/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── ip-geo.test.ts
│ ├── news/
│ │ └── rss-feed/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── rss-feed.test.ts
│ ├── reference/
│ │ └── dictionary/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── dictionary.test.ts
│ ├── registry/
│ │ ├── github/
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── github.test.ts
│ │ ├── npm/
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── npm.test.ts
│ │ └── pypi/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── pypi.test.ts
│ ├── social/
│ │ └── hacker-news/
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── hacker-news.test.ts
│ └── weather/
│ └── forecast/
│ ├── index.ts
│ ├── types.ts
│ └── forecast.test.ts
├── types/
│ └── common.ts
└── utils/
├── parse.ts
├── parse.test.ts
└── url.ts
- Create a new folder under
src/scrapers/{category}/{name}/ - Add
types.tswith your type definitions - Implement the scraper in
index.ts - Write tests in
{name}.test.ts - Export types from
src/types/index.ts - Export functions from
src/index.ts - Add an entry to
tsup.config.tsandpackage.jsonexports
npm install
npm run typecheck
npm run lint
npm run test
npm run test:watch
npm run buildMIT © ptheus