-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
2 changed files
with
126 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 源。', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} <br> | ||
Location: ${location} <br> | ||
Date and Time: ${dateTimeStr} | ||
`, | ||
pubDate, | ||
}; | ||
|
||
return item; | ||
}); | ||
|
||
return { | ||
title: `UPS Tracking - ${trackingNumber}`, | ||
link: url, | ||
item: items, | ||
}; | ||
} |