tu-scraper
v1.2.1
Published
Production-ready TypeScript scraper and npm package to fetch official Tribhuvan University (TU) notices across all 9 verified institute, faculty and central office portals.
Maintainers
Readme
Overview
tu-scraper provides a clean, unified, zero-boilerplate API for developers, students, and institutions to programmatically access official Tribhuvan University notices. It eliminates manual HTML parsing, handles faculty-specific DOM variance, provides in-memory caching, and standardizes notice records into strict TypeScript interfaces.
Installation
# npm
npm install tu-scraper
# pnpm
pnpm add tu-scraper
# yarn
yarn add tu-scraper
# bun
bun add tu-scraperQuick Start
import { getNotices, getLatest, searchNotices } from "tu-scraper";
// 1. Fetch notices from Institute of Science and Technology (IOST)
const iostNotices = await getNotices("iost");
console.log(iostNotices);
// 2. Fetch the single most recent notice from Institute of Engineering (IOE)
const latestIoe = await getLatest("ioe");
console.log(`Latest IOE Notice: ${latestIoe?.title} (${latestIoe?.date})`);
// 3. Search for a specific program/keyword across all 9 institutions
const bscResults = await searchNotices("BSc CSIT");
// 4. Search within a specific faculty
const examNotices = await searchNotices("exam", "fohss");Supported Sources (9 Official Portals)
tu-scraper strictly targets only the 9 verified official Tribhuvan University portals:
| Source Identifier | Institution Name | Category | Official Portal URL |
| :--- | :--- | :--- | :--- |
| iost | Institute of Science and Technology | Institute | https://iost.tu.edu.np/notices |
| fohss | Faculty of Humanities and Social Sciences | Faculty | https://fohss.tu.edu.np/notices |
| ioe | Institute of Engineering | Institute | https://ioe.tu.edu.np/notices |
| ac | Amrit Science Campus | Campus | https://ac.tu.edu.np/notices |
| iaas | Institute of Agriculture and Animal Science | Institute | https://iaas.tu.edu.np/notices |
| iof | Institute of Forestry | Institute | https://iof.tu.edu.np/notices |
| foe | Faculty of Education | Faculty | https://foe.tu.edu.np/notices |
| fol | Faculty of Law | Faculty | https://fol.tu.edu.np/notices |
| tu | Tribhuvan University (Central Office) | University | https://tu.edu.np/notices |
| all | Aggregates all 9 institutions | Meta-source | All supported URLs above |
Return Type (Notice)
Every scraper normalizes results into the standard Notice contract:
export interface Notice {
/** Unique ID extracted from notice detail URL */
id: string;
/** Full notice title (whitespace cleaned and trimmed) */
title: string;
/** Origin source identifier */
source: NoticeSource;
/** Date of publication (if available on source page, else undefined) */
date?: string;
/** Canonical URL to notice page */
url: string;
/** Direct PDF or attachment link (if available, else undefined) */
pdf?: string;
}Note: In accordance with data integrity rules, if a date or PDF is unavailable on the source webpage, the property is left undefined rather than populating fabricated data.
Core API Reference
getNotices(source, options?)
Fetches all active notices from the specified source or all sources combined.
function getNotices(
source: "iost" | "fohss" | "ioe" | "ac" | "iaas" | "iof" | "foe" | "fol" | "tu" | "all",
options?: ScrapeOptions
): Promise<Notice[]>;Behavior with "all":
When calling await getNotices("all"), scrapers run concurrently across all 9 portals via Promise.allSettled. If an individual faculty server is temporarily unreachable, the remaining successful faculty results are combined and returned gracefully.
getLatest(source, options?)
Fetches the single latest notice from the given faculty.
function getLatest(
source: SourceQuery,
options?: ScrapeOptions
): Promise<Notice | null>;searchNotices(query, source?, options?)
Performs a case-insensitive title search against notices from the specified source (default: "all").
function searchNotices(
query: string,
source?: SourceQuery, // Defaults to "all"
options?: ScrapeOptions
): Promise<Notice[]>;clearCache()
Clears the internal in-memory cache manually.
import { clearCache } from "tu-scraper";
clearCache();ScrapeOptions
Customize network timeouts and cache behavior:
export interface ScrapeOptions {
/** Request timeout in milliseconds (default: 10000ms) */
timeout?: number;
/** Bypass internal in-memory cache (default: false) */
bypassCache?: boolean;
/** Custom User-Agent header */
userAgent?: string;
}Error Handling
tu-scraper exports clear, typed errors:
| Error Class | Trigger | Properties |
| :--- | :--- | :--- |
| InvalidSourceError | When an unsupported source string is provided | invalidSource, allowedSources |
| NetworkError | When an HTTP request fails or status code is not 200 | url, statusCode, originalError |
| TimeoutError | When a request exceeds the specified timeout | url, timeoutMs |
| ParseError | When the webpage structure is corrupted | source, url |
import { getNotices, InvalidSourceError, NetworkError, TimeoutError } from "tu-scraper";
try {
const notices = await getNotices("ioe", { timeout: 5000 });
} catch (error) {
if (error instanceof InvalidSourceError) {
console.error(`Invalid source: ${error.invalidSource}`);
} else if (error instanceof TimeoutError) {
console.error(`Request timed out after ${error.timeoutMs}ms`);
} else if (error instanceof NetworkError) {
console.error(`Network error: ${error.message} (HTTP ${error.statusCode})`);
}
}Examples
1. Daily Telegram / Discord Bot Notification
import { getLatest } from "tu-scraper";
async function notifyLatestNotice() {
const latest = await getLatest("ioe");
if (latest) {
await sendDiscordWebhook({
content: `📢 **New IOE Notice**: ${latest.title}\n🔗 ${latest.url}\n📅 ${latest.date || "N/A"}`
});
}
}2. Express.js REST API Microservice
import express from "express";
import { getNotices, searchNotices, isValidSource } from "tu-scraper";
const app = express();
app.get("/api/notices/:source", async (req, res) => {
const { source } = req.params;
if (!isValidSource(source)) {
return res.status(400).json({ error: "Invalid source" });
}
const data = await getNotices(source);
res.json({ success: true, count: data.length, data });
});
app.get("/api/search", async (req, res) => {
const q = String(req.query.q || "");
const src = (req.query.source as any) || "all";
const results = await searchNotices(q, src);
res.json({ query: q, results });
});
app.listen(8080, () => console.log("TU Notices API running on port 8080"));Testing
The package includes a comprehensive test suite with 30 unit tests covering:
- All 9 source adapters using real saved HTML fixtures
- Notice normalization and schema compliance
- Search matching & edge cases
getLatestbehavior- Invalid source error assertions
- Caching TTL and invalidation
"all"multi-source aggregation
Run the test suite:
npm testChangelog
See CHANGELOG.md for the full release history.
License
MIT © Ankit Khatri KC
