From 38ec24db20a1edfccc4a59f52685f2736e2da0f3 Mon Sep 17 00:00:00 2001 From: JIAXIAN LI Date: Sun, 5 Jan 2025 07:21:59 -0800 Subject: [PATCH] feat(route): Add New Route for UPS Tracking (#17941) * New Routes for UPS Tracking * New Routes for UPS Tracking * trying to fix Vitest puppeteer on Node lts/-1 * Add tracking feature to UPS route - Implemented tracking functionality in `lib/routes/ups/track.ts` * fix: update tracking logic in UPS route * Add guid to track request --- lib/routes/ups/namespace.ts | 12 ++++ lib/routes/ups/track.ts | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 lib/routes/ups/namespace.ts create mode 100644 lib/routes/ups/track.ts diff --git a/lib/routes/ups/namespace.ts b/lib/routes/ups/namespace.ts new file mode 100644 index 00000000000000..21b8d8adef2ada --- /dev/null +++ b/lib/routes/ups/namespace.ts @@ -0,0 +1,12 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'UPS', + url: 'ups.com', + description: 'United Parcel Service (UPS) updates, news, and tracking RSS feeds.', + + zh: { + name: 'UPS(联合包裹服务公司)', + description: '联合包裹服务公司(UPS)的更新、新闻和追踪 RSS 源。', + }, +}; diff --git a/lib/routes/ups/track.ts b/lib/routes/ups/track.ts new file mode 100644 index 00000000000000..d04352d6fe674d --- /dev/null +++ b/lib/routes/ups/track.ts @@ -0,0 +1,114 @@ +import { Route, DataItem } from '@/types'; +import { load } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; +import puppeteer from '@/utils/puppeteer'; + +export const route: Route = { + path: '/track/:trackingNumber', + categories: ['other'], + example: '/ups/track/1Z78R6790470567520', + parameters: { trackingNumber: 'The UPS tracking number (e.g., 1Z78R6790470567520).' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: 'Tracking', + maintainers: ['Aquabet'], + handler, +}; + +async function handler(ctx) { + const { trackingNumber } = ctx.req.param(); + const url = `https://www.ups.com/track?loc=en_US&tracknum=${trackingNumber}`; + + const browser = await puppeteer(); + const page = await browser.newPage(); + + await page.setRequestInterception(true); + + // skip loading images, stylesheets, and fonts + page.on('request', (request) => { + if (['image', 'stylesheet', 'font', 'ping', 'fetch'].includes(request.resourceType())) { + request.abort(); + } else { + request.continue(); + } + }); + + await page.goto(url, { waitUntil: 'domcontentloaded' }); + + const viewDetailsButton = '#st_App_View_Details'; + try { + await page.waitForSelector(viewDetailsButton); + await page.click(viewDetailsButton); + } catch { + return { + title: `UPS Tracking - ${trackingNumber}`, + link: url, + item: [], + }; + } + + await page.waitForSelector('tr[id^="stApp_activitydetails_row"]'); + + const content = await page.content(); + await browser.close(); + + const $ = load(content); + + const rows = $('tr[id^="stApp_activitydetails_row"]'); + + const items: DataItem[] = rows.toArray().map((el, i) => { + const dateTimeRaw = $(el).find(`#stApp_activitiesdateTime${i}`).text() || 'Not Provided'; + + const dateTimeStr = dateTimeRaw + .trim() + .replace(/(\d{1,}\/\d{1,}\/\d{4})(\d{1,}:\d{1,}\s[AP]\.?M\.?)/, '$1 $2') + .replaceAll('P.M.', 'PM') + .replaceAll('A.M.', 'AM'); + + const pubDate = parseDate(dateTimeStr); + + const activityCellText = $(el) + .find(`#stApp_milestoneActivityLocation${i}`) + .text() + .trim() + .replaceAll(/\s*\n+\s*/g, '\n'); + + const lines = activityCellText + .split('\n') + .map((l) => l.trim()) + .filter(Boolean); + + // Situation 0: There is text within the strong element + // Example: ["Delivered", "DELIVERED", "REDMOND, WA, United States"] + // Situation 1: strong is empty => the first line in lines is the status + // Example: ["Departed from Facility", "Seattle, WA, United States"] + const status = lines[0]; + const location = lines.at(-1) || ''; + + const item: DataItem = { + title: status, + link: url, + guid: `${trackingNumber}-${i}`, + description: ` + Status: ${status}
+ Location: ${location}
+ Date and Time: ${dateTimeStr} + `, + pubDate, + }; + + return item; + }); + + return { + title: `UPS Tracking - ${trackingNumber}`, + link: url, + item: items, + }; +}