diff --git a/assets/radar-rules.js b/assets/radar-rules.js index 6c7ee03ef..1238c133e 100644 --- a/assets/radar-rules.js +++ b/assets/radar-rules.js @@ -1733,13 +1733,13 @@ 'trackings.post': [ { title: '郵便・荷物の追跡', - docs: 'https://docs.rsshub.app/other.html#ri-ben-you-bian', + docs: 'https://docs.rsshub.app/other.html#ri-ben-you-bian-you-bian-zhui-ji-サービス', source: '/services/srv/search/direct', target: (params, url) => { const reqCode = new URL(url).searchParams.get('reqCodeNo1').toUpperCase(); const locale = new URL(url).searchParams.get('locale').toLowerCase(); - if ((reqCode.search(/^(?:\d{12}|[A-Z]{2}\d{9}[A-Z]{2})$/) === 0 && locale === 'ja') || locale === 'en') { - return `/japanpost/${reqCode}/${locale}`; + if ((reqCode.search(/^(?:\d{11,12}|[A-Z]{2}\d{9}[A-Z]{2})$/) === 0 && locale === 'ja') || locale === 'en') { + return `/japanpost/track/${reqCode}/${locale}`; } }, }, diff --git a/docs/en/other.md b/docs/en/other.md index 9d2652760..18067718b 100644 --- a/docs/en/other.md +++ b/docs/en/other.md @@ -104,7 +104,7 @@ Official Website: [https://www.ssm.gov.mo/apps1/PreventWuhanInfection/en.aspx](h ### Track & Trace Service - + | Japanese | English | | -------- | ------- | diff --git a/docs/other.md b/docs/other.md index d866ef1fd..1a444879d 100644 --- a/docs/other.md +++ b/docs/other.md @@ -686,7 +686,7 @@ type 为 all 时,category 参数不支持 cost 和 free ### 郵便追跡サービス - + | 日语 | 英语 | | ---- | ---- | diff --git a/lib/router.js b/lib/router.js index 1a6af8a81..f3e96b3ca 100644 --- a/lib/router.js +++ b/lib/router.js @@ -2082,7 +2082,7 @@ router.get('/leboncoin/ad/:query', require('./routes/leboncoin/ad.js')); router.get('/dhl/:id', require('./routes/dhl/shipment-tracking')); // Japanpost -router.get('/japanpost/:reqCode/:locale?', require('./routes/japanpost/index')); +router.get('/japanpost/track/:reqCode/:locale?', require('./routes/japanpost/track')); // 中华人民共和国商务部 router.get('/mofcom/article/:suffix', require('./routes/mofcom/article')); diff --git a/lib/routes/japanpost/index.js b/lib/routes/japanpost/index.js deleted file mode 100644 index 0bb190944..000000000 --- a/lib/routes/japanpost/index.js +++ /dev/null @@ -1,67 +0,0 @@ -const got = require('@/utils/got'); -const cheerio = require('cheerio'); -const utils = require('./utils'); - -const baseTitle = '日本郵便'; -const baseUrl = 'https://trackings.post.japanpost.jp/services/srv/search/direct?'; - -const util = new utils(); -util.expandEven(); -util.expandReverse(); - -module.exports = async (ctx) => { - const reqCode = ctx.params.reqCode; - const reqReqCode = 'reqCodeNo1=' + reqCode; - let locale; - switch (ctx.params.locale) { - case 'ja': - locale = 'ja'; - break; - case 'en': - locale = 'en'; - break; - default: - locale = 'ja'; - } - const reqLocale = '&locale=' + locale; - const link = baseUrl + reqReqCode + reqLocale; - - const response = await got({ - method: 'get', - url: link, - }); - - const $ = cheerio.load(response.data); - const list = $('.tableType01').eq(1).find('tr').slice(2); - - const listEven = list.even(); - - const packageType = $('.tableType01').eq(0).find('tr').eq(1).find('td').eq(1).text(); - - ctx.state.data = { - title: `${baseTitle} ${packageType} ${reqCode}`, - link: link, - description: 'Reserved for further development.', - item: listEven - .reverse() - .map((index, item) => { - item = $(item); - const itemTd = item.find('td'); - const packageStatus = itemTd.eq(1).text().trim(); - const packageRegion = itemTd.eq(4).text().trim(); - const packageOffice = itemTd.eq(3).text().trim(); - const itemTitle = `${packageStatus} ${packageRegion} ${packageOffice}`; - const itemDescription = itemTd.eq(2).text(); - const itemPubDateText = itemTd.eq(0).text(); - const itemGuid = util.generateGuid(itemTitle + itemDescription + itemPubDateText); - return { - title: itemTitle, - description: itemDescription, - pubDate: new Date(itemPubDateText), - link: link, - guid: itemGuid.slice(0, 32), - }; - }) - .get(), - }; -}; diff --git a/lib/routes/japanpost/track.js b/lib/routes/japanpost/track.js new file mode 100644 index 000000000..cd2d8dcb7 --- /dev/null +++ b/lib/routes/japanpost/track.js @@ -0,0 +1,106 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const utils = require('./utils'); + +let baseTitle = '日本郵便'; +const baseUrl = 'https://trackings.post.japanpost.jp/services/srv/search/direct?'; + +utils.expandEven(); +utils.expandOdd(); + +module.exports = async (ctx) => { + const reqCode = ctx.params.reqCode; + const reqReqCode = 'reqCodeNo1=' + reqCode; + + let locale = 'ja'; + if (ctx.params.locale === 'en') { + locale = 'en'; + baseTitle = 'Japanpost'; + } + const reqLocale = '&locale=' + locale; + + const link = baseUrl + reqReqCode + reqLocale; + + const response = await got({ + method: 'get', + url: link, + }); + + const $ = cheerio.load(response.data); + const list = $('.tableType01').eq(1).find('tr').slice(2); + const officeList = $('.tableType03').eq(0).find('tr').slice(1); + + if (!list.length) { + const resErrorText = $('.tableType01').eq(0).find('tr').eq(2).find('td').eq(1).text().trim(); + throw new Error(resErrorText); + } + + const listEven = list.even(); + const listOdd = list.odd(); + + const packageType = $('.tableType01').eq(0).find('tr').eq(1).find('td').eq(1).text().trim(); + + let lastItemTimeStamp; + let tz; + + ctx.state.data = { + title: `${baseTitle} ${packageType} ${reqCode}`, + link: link, + description: `${baseTitle} ${packageType} ${reqCode}`, + language: locale, + item: listEven + .map((index, item) => { + const itemTd = $(item).find('td'); + const packageStatus = itemTd.eq(1).text().trim(); + const packageRegion = itemTd.eq(4).text().trim(); + const packageOffice = itemTd.eq(3).text().trim(); + const packageOfficeZipCode = listOdd.eq(index).find('td').eq(0).text().trim(); + const itemTitle = `${packageStatus} ${packageOffice} ${packageRegion}`; + const packageTrackRecord = itemTd.eq(2).text().trim(); + let itemDescription = `${packageStatus}
`; + itemDescription += packageTrackRecord ? `${packageTrackRecord}
` : ''; + itemDescription += packageOfficeZipCode ? `${packageOfficeZipCode} ` : ''; + itemDescription += packageOffice ? `${packageOffice} ` : ''; + itemDescription += `${packageRegion}`; + + if (index === 0) { + if (officeList.length) { + const officeItemList = officeList + .map((i, e) => { + const eTd = $(e).find('td'); + let officeItem = eTd.eq(0).text().trim(); + officeItem += ` ${eTd.eq(1).html().trim()}`; + officeItem += ` ${eTd.eq(2).html().trim()}`; + return officeItem; + }) + .get(); + itemDescription += '
'; + for (const i of officeItemList) { + itemDescription += `
${i}`; + } + } + } + + const itemPubDateText = itemTd.eq(0).text().trim(); + const itemGuid = utils.generateGuid(itemTitle + itemDescription + itemPubDateText); + + let thisItemTimeStamp; + [thisItemTimeStamp, tz] = utils.parseDatetime(itemPubDateText, packageOffice, packageRegion, tz, locale); + if (lastItemTimeStamp) { + if (thisItemTimeStamp <= lastItemTimeStamp) { + thisItemTimeStamp = lastItemTimeStamp + 1000; + } + } + lastItemTimeStamp = thisItemTimeStamp; + + return { + title: itemTitle, + description: itemDescription, + pubDate: new Date(thisItemTimeStamp), + link: link, + guid: itemGuid.slice(0, 32), + }; + }) + .get(), + }; +}; diff --git a/lib/routes/japanpost/utils.js b/lib/routes/japanpost/utils.js index 07c3cee22..930762982 100644 --- a/lib/routes/japanpost/utils.js +++ b/lib/routes/japanpost/utils.js @@ -1,8 +1,17 @@ const cheerio = require('cheerio'); const crypto = require('crypto'); -function utils() { - this.expandOdd = function () { +const cityTimezones = require('city-timezones'); +const dayjs = require('dayjs'); +const customParseFormat = require('dayjs/plugin/customParseFormat'); +const utc = require('dayjs/plugin/utc'); +const timezone = require('dayjs/plugin/timezone'); +dayjs.extend(customParseFormat); +dayjs.extend(utc); +dayjs.extend(timezone); + +const utils = { + expandOdd: () => { cheerio.prototype.odd = function () { const odds = []; this.each(function (index, item) { @@ -12,9 +21,8 @@ function utils() { }); return cheerio(odds); }; - }; - - this.expandEven = function () { + }, + expandEven: () => { cheerio.prototype.even = function () { const evens = []; this.each(function (index, item) { @@ -24,9 +32,8 @@ function utils() { }); return cheerio(evens); }; - }; - - this.expandReverse = function () { + }, + expandReverse: () => { cheerio.prototype.reverse = function () { const reverses = []; this.each(function (index, item) { @@ -35,14 +42,53 @@ function utils() { reverses.reverse(); return cheerio(reverses); }; - }; - - this.generateGuid = function (t) { + }, + generateGuid: (t) => { const hash = crypto.createHash('sha512'); hash.update(t); const r = hash.digest('hex').toUpperCase(); return r; - }; -} + }, + parseDatetime: (t, o, r, tz, l) => { + const formatJaDate = 'YYYY/MM/DD'; + const formatJaDateTime = 'YYYY/MM/DD HH:mm'; + const formatEnDate = 'MM/DD/YYYY'; + const formatEnDateTime = 'MM/DD/YYYY HH:mm'; + let customFormat; + + switch (l) { + case 'ja': + customFormat = dayjs(t, formatJaDate, true).isValid() ? formatJaDate : dayjs(t, formatJaDateTime, true).isValid() ? formatJaDateTime : undefined; + break; + case 'en': + customFormat = dayjs(t, formatEnDate, true).isValid() ? formatEnDate : dayjs(t, formatEnDateTime, true).isValid() ? formatEnDateTime : undefined; + break; + } + if (!customFormat) { + return [new Date(t).getTime(), tz]; + } + + if (o) { + if (o.indexOf('郵便局') !== -1) { + tz = 'Asia/Tokyo'; + } else { + const oS = o.replace(' EMS', '').replace(' INT', ''); + try { + try { + tz = cityTimezones.lookupViaCity(oS)[0].timezone; + } catch { + tz = cityTimezones.lookupViaCity(r)[0].timezone; + } + } catch { + // empty + } + } + } + return [ + dayjs.tz(t, customFormat, tz).valueOf(), + tz, + ]; + }, +}; module.exports = utils; diff --git a/package.json b/package.json index cdea634db..7a9e5b3ab 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "bbcodejs": "0.0.4", "cheerio": "1.0.0-rc.3", "chrono-node": "2.2.6", + "city-timezones": "^1.2.0", "crypto-js": "4.0.0", "currency-symbol-map": "4.0.4", "dayjs": "1.10.3",