Skip to content

Commit

Permalink
feat: add kuaidi100 (DIYgod#4020)
Browse files Browse the repository at this point in the history
  • Loading branch information
NeverBehave authored Feb 19, 2020
1 parent da288c5 commit dcab1c4
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 0 deletions.
14 changes: 14 additions & 0 deletions docs/other.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,17 @@ type 为 all 时,category 参数不支持 cost 和 free
### はてな匿名ダイアリー - 人気記事アーカイブ

<Route author="masakichi" example="/hatena/anonymous_diary/archive" path="/hatena/anonymous_diary/archive"/>

## 快递 100

### 快递订单追踪

<Route author="NeverBehave" example="/kuaidi100/track/shunfeng/SF1007896781640/0383" path="/track/:number/:id/:phone?" :paramsDesc="['快递公司代号', '订单号', '手机号后四位(仅顺丰)']">

快递公司代号如果不能确定,可通过下方快递列表获得。

</Route>

### 支持的快递公司列表

<Route author="NeverBehave" example="/kuaidi100/company" path="/kuaidi100/company"/>
4 changes: 4 additions & 0 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -2260,6 +2260,10 @@ router.get('/haohaozhu/discover/:keyword?', require('./routes/haohaozhu/discover
// 东北大学
router.get('/neu/news/:type', require('./routes/universities/neu/news'));

// 快递100
router.get('/kuaidi100/track/:number/:id/:phone?', require('./routes/kuaidi100/index'));
router.get('/kuaidi100/company', require('./routes/kuaidi100/supported_company'));

// 稻草人书屋
router.get('/dcrsw/:name/:count?', require('./routes/novel/dcrsw'));

Expand Down
51 changes: 51 additions & 0 deletions lib/routes/kuaidi100/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const utils = require('./utils');

module.exports = async (ctx) => {
// number is shorthand for company
// id is ticket number
// phone for shunfeng :)
const { number, id, phone } = ctx.params;

// I am doing these to avoid invaild request.
// First, check if code is vaild
const { status, message, company } = await utils.checkCode(ctx, number, id, phone);

let data;
let query;
const time = new Date().toString();

if (status) {
query = await utils.getQuery(ctx, number, id, phone);
if (query.status !== '200') {
data = [
{
context: query.message,
time,
},
];
} else {
data = query.data;
}
} else {
data = [
{
context: message,
time,
},
];
}

// Maybe we can look into isCheck, condition, and state :)
// But I just want to make it work for now.
ctx.state.data = {
title: `快递 ${company.name}-${number}`,
link: 'https://www.kuaidi100.com',
description: `快递 ${company.name}-${number}`,
item: data.map((item) => ({
title: item.context,
description: item.context,
pubDate: new Date(item.time || item.ftime).toUTCString(),
link: 'https://www.kuaidi100.com',
})),
};
};
17 changes: 17 additions & 0 deletions lib/routes/kuaidi100/supported_company.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const utils = require('./utils');

module.exports = async (ctx) => {
const ls = await utils.company(ctx);
ctx.state.data = {
title: `快递100 快递列表`,
link: 'https://www.kuaidi100.com',
description: `快递100 所支持的快递列表及其查询名称`,
item: ls.map((item) => ({
title: item.name,
description: item.number,
category: item.comTypeName,
pubDate: new Date().toUTCString(),
link: item.siteUrl,
})),
};
};
216 changes: 216 additions & 0 deletions lib/routes/kuaidi100/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
const got = require('@/utils/got');
const wwwid_key = 'kuaidi100-wwwid';
const csrf_key = 'kuaidi100-csrf';
const query_count = 'kuaidi100-cookie-count';
const max_query_count = 30;

async function getCookie(ctx) {
// Check if this key should be replace? every 30 times should be fine.
await shouldUpdateCookie(ctx);
let wwwid = await ctx.cache.get(wwwid_key);
let csrf = await ctx.cache.get(csrf_key);
if (!wwwid || !csrf) {
const indexResponse = await got({
method: 'get',
url: 'https://www.kuaidi100.com/?from=appstore',
headers: {
// App store
Referer: 'https://apps.apple.com/cn/app/%E5%BF%AB%E9%80%92100-%E5%8F%8C11%E5%AF%84%E4%BB%B6%E9%80%80%E8%B4%A7-%E4%B8%8A%E5%BF%AB%E9%80%92100/id458270120',
},
});
const set_cookie = indexResponse.headers['set-cookie'];
if (set_cookie) {
for (const e of set_cookie) {
if (e.indexOf('WWWID') === 0) {
wwwid = e.split(';')[0];
} else if (e.indexOf('csrftoken') === 0) {
csrf = e.split(';')[0];
}
}
}

ctx.cache.set(wwwid_key, wwwid, 600);
ctx.cache.set(csrf_key, csrf, 600);
// We have acquired new cookie. It may due to cache timeout.
// Force update counter and don't wait it finished.
shouldUpdateCookie(ctx, true);
}

return {
wwwid,
csrf,
};
}

/*
Example Company
{
"available": false, <- 这个不知道是什么的可用性,反正不是可查询的
"canOrder": false,
"comTypeName": "国内运输商",
"contactTel": "626-818-2750",
"createTime": 1486202888000,
"hasNetwork": false,
"id": 7172420,
"name": "金岸物流",
"number": "jinan",
"shortName": "金岸物流",
"shortNumber": "jinan",
"siteUrl": "http://www.gpl-express.com/",
"type": 0,
"version": 776
}
*/
async function getCompanyList(ctx) {
// Using date as cache key and it will automatically expired by 1d
const key = `kuaidi100-company-name-${new Date().toISOString().split('T')[0]}`;
let list = await ctx.cache.get(key);
if (!list) {
const cookie = await getCookie(ctx);
const wwwid = cookie.wwwid;
const companyResponse = await got({
method: 'post',
url: 'https://www.kuaidi100.com/company.do?method=js&t=201701051440',
headers: {
Referer: 'https://www.kuaidi100.com/',
Cookie: wwwid,
},
});
list = companyResponse.body;

// Parsing the js file
try {
list = list
.substr(12)
.replace(/};/g, '}')
.replace(/'/g, '"');
list = JSON.parse(list);
list = list.company;
} catch (e) {
list = [];
}

ctx.cache.set(key, list);
} else {
list = JSON.parse(list);
}
return list;
}

async function shouldUpdateCookie(ctx, forcedUpdate = false) {
if (forcedUpdate) {
ctx.cache.set(query_count, 0);
} else {
const count = ctx.cache.get(query_count);
if (!count) {
ctx.cache.set(query_count, 1);
} else {
if (count > max_query_count) {
ctx.cache.set(query_count, 0);
await clearCookie(ctx);
} else {
ctx.cache.set(query_count, count + 1);
}
}
}
}

async function clearCookie(ctx) {
ctx.cache.set(wwwid_key, null);
ctx.cache.set(csrf_key, null);
}

module.exports = {
company: async (ctx) => await getCompanyList(ctx),
checkCode: async (ctx, number, id, phone) => {
const list = await getCompanyList(ctx);
const company = list.find((c) => c.number === number);
if (!company) {
return {
status: false,
message: '快递公司编号不受支持!',
company: {
name: '未知',
},
};
} else {
if (number.indexOf('shunfeng') !== -1 && !isNaN(phone) && String(phone).length !== 4) {
return {
status: false,
message: '顺丰查询需要手机号后四位!',
company,
};
} else if (company.checkReg) {
return {
status: true,
regex: new RegExp(company.checkReg).test(id),
company,
};
} else {
return {
status: true,
regex: undefined,
company,
};
}
}
},

/*
Example Query
{
"message": "ok",
"nu": "73123917441103",
"ischeck": "1",
"condition": "F00",
"com": "zhongtong",
"status": "200",
"state": "3",
"data": [{
"time": "2019-12-12 12:41:15",
"ftime": "2019-12-12 12:41:15",
"context": "【温州市】 快件已由【菜鸟的温州燎原华庭7栋103号店】代签收, 如有问题请电联(18581563547 / 4001787878), 感谢您使用中通快递, 期待再次为您服务!",
"location": ""
}
...]
Example failed Query
{
"message": "快递公司参数异常:验证码错误",
"nu": "",
"ischeck": "0",
"condition": "",
"com": "",
"status": "408",
"state": "0",
"data": []
}
*/

getQuery: async (ctx, number, id, phone) => {
const query_key = `kuaidi100-query-${number}-${id}`;
let query = await ctx.cache.get(query_key);
if (!query) {
const cookie = await getCookie(ctx);
const queryResponse = await got({
method: 'get',
url: `https://www.kuaidi100.com/query?type=${number}&postid=${id}&temp=${Math.random()}&phone=${phone ? phone : ''}`,
headers: {
Referer: 'https://www.kuaidi100.com/',
Cookie: `${cookie.csrf}; ${cookie.wwwid}`,
},
});

query = queryResponse.data;
if (query.ischeck === '0' && query.status === '200') {
// Not yet complete, don't cache for now.
// Per owner suggestion
} else {
ctx.cache.set(query_key, query); // Finished, or error id
}
} else {
query = JSON.parse(query);
}

return query;
},
};

0 comments on commit dcab1c4

Please sign in to comment.