Repository navigation
Expand file tree
/
Copy pathinfo.js
More file actions
111 lines (104 loc) · 3.81 KB
/
Copy pathinfo.js
File metadata and controls
111 lines (104 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* shipxy info — 查询船舶完整参数(登录态)
*
* 输入:MMSI 列表(逗号/空格分隔);也支持 --kw(会先做搜索取第一条)
* 输出:船长/船宽/吃水 + 位置 + 航速航向 + 目的地 + 更新时间
*
* 策略:INTERCEPT + browser(复用登录 Chrome,通过页面自身 jQuery.ajax
* 让 setHeaders 自动加 s+t 签名 header)
*
* 例:
* opencli shipxy info --mmsi 413543265
* opencli shipxy info --mmsi "413543265,574952645"
* opencli shipxy info --kw COSCO12
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
import {
ensureShipxyReady,
fetchShipmByMmsi,
normalizeShipm,
searchByKeyword,
} from './shared/helpers.js';
cli({
site: 'shipxy',
name: 'info',
access: 'read',
description: '船讯网船舶完整信息:船参数 + 实时位置(需先在 Chrome 登录 shipxy.com)',
domain: 'www.shipxy.com',
strategy: Strategy.INTERCEPT,
browser: true,
example: 'opencli shipxy info --mmsi "413543265,574952645"',
args: [
{ name: 'mmsi', type: 'string', default: '', help: 'MMSI 列表(逗号/空格分隔),与 --kw 二选一' },
{ name: 'kw', type: 'string', default: '', help: '关键字(先搜索再取每个 kw 的第一条),与 --mmsi 二选一' },
],
columns: [
'mmsi', 'imo', 'name', 'typeLabel',
'length_m', 'width_m', 'draught_m',
'lat', 'lon', 'sog_kn', 'cog_deg', 'hdg_deg',
'dest', 'eta', 'lastPositionUtc',
],
func: async (page, args) => {
const mmsiRaw = String(args.mmsi ?? '').trim();
const kwRaw = String(args.kw ?? '').trim();
if (!mmsiRaw && !kwRaw) {
throw new ArgumentError('必须提供 --mmsi 或 --kw');
}
// 1. 收集 MMSI 列表
let mmsiList = [];
if (mmsiRaw) {
mmsiList = mmsiRaw.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
// 简单校验:MMSI 通常 9 位数字
for (const m of mmsiList) {
if (!/^\d{7,9}$/.test(m)) {
throw new ArgumentError(`MMSI 格式非法:${m}(应为 7~9 位数字)`);
}
}
} else {
const kws = kwRaw.split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
for (const kw of kws) {
const ships = await searchByKeyword(kw, { limit: 1 });
if (ships.length && ships[0].m) mmsiList.push(String(ships[0].m));
}
if (mmsiList.length === 0) {
throw new EmptyResultError('shipxy info', `--kw 未搜到任何船:${kwRaw}`);
}
}
// 去重
mmsiList = Array.from(new Set(mmsiList));
// 2. 保证浏览器 tab 就绪 + 已登录
await ensureShipxyReady(page);
// 3. 逐个查询(GetShipm 一次一个 mmsi)
const rows = [];
for (const mmsi of mmsiList) {
try {
const raw = await fetchShipmByMmsi(page, mmsi);
const norm = normalizeShipm(raw);
if (!norm) {
rows.push({
mmsi, imo: '', name: '[no-data]', typeLabel: '',
length_m: null, width_m: null, draught_m: null,
lat: null, lon: null, sog_kn: null, cog_deg: null, hdg_deg: null,
dest: '', eta: '', lastPositionUtc: '',
});
} else {
rows.push(norm);
}
} catch (e) {
// 401 之类的鉴权错误直接抛出,让用户重新登录
if (e && e.name === 'AuthRequiredError') throw e;
rows.push({
mmsi, imo: '', name: `[error] ${e.message || e}`, typeLabel: '',
length_m: null, width_m: null, draught_m: null,
lat: null, lon: null, sog_kn: null, cog_deg: null, hdg_deg: null,
dest: '', eta: '', lastPositionUtc: '',
});
}
}
if (rows.length === 0) {
throw new EmptyResultError('shipxy info', '没有查到任何船舶信息');
}
return rows;
},
});