-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
190 lines (144 loc) · 5.89 KB
/
index.ts
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import axios from 'axios';
import { wrapper } from 'axios-cookiejar-support';
import { CookieJar } from 'tough-cookie';
import { Cheerio, Element, load } from 'cheerio'
import prompts from 'prompts';
import fs from 'fs'
import { AdifFormatter } from 'adif-parser-ts'
const jar = new CookieJar();
const client = wrapper(axios.create({ jar }));
const loginURL = 'https://www.qrz.com/login'
const logBookURL = 'http://logbook.qrz.com'
const rowDecoding = [
[],
[ {type: 'single', find: 'QSO Start', save: 'qso_date'}, {type: 'single', find: 'Confirmed', save: 'qsl_rcvd'} ],
[ {type: 'single', find: 'QSO End', save: 'qso_date_off'} ],
[], [], [], [], [], //Skip unused rows
[ {type: 'double', find: 'Station', saveTO: 'call', saveFROM: 'station_callsign' } ],
[], [], [], [],
[ {type: 'quad', find: 'Frequency', saveTO: 'freq', saveFROM: 'freq_rx',
find2: 'Mode', save2TO: 'mode', save2FROM: 'mode'}],
[ {type: 'quad', find: 'Power', saveTO: 'pwr', saveFROM: 'tx_pwr',
find2: 'RST', save2TO: 'rst_rcvd', save2FROM: 'rst_sent'}],
[],
[ {type: 'quad', find: 'Grid', saveTO: 'gridsquare', saveFROM: 'my_gridsquare',
find2: 'Distance', save2TO: 'distance', save2FROM: 'distance'}],
]
function findWithString(element: Cheerio<Element>, s: string) {
return element.filter(function() {
return $(this).text().trim().startsWith(s)
}).first()
}
function getTextOnly(element: Cheerio<Element>) {
return element.contents().filter(function() {
return this.type === 'text';
}).text().trim()
}
function decodeRow(row: Cheerio<Element>, i: number, data: any) {
for (const ins of rowDecoding[i]) {
switch (ins.type) {
case 'single': {
const tIns = (ins as singleType)
data[tIns.save] = findWithString(row, tIns.find).next().text()
} break;
case 'double': {
const tIns = (ins as doubleType)
const found = findWithString(row, tIns.find)
data[tIns.saveTO] = found.next().text()
data[tIns.saveFROM] = found.next().next().text()
} break;
case 'quad': {
const tIns = (ins as quadType)
const first = findWithString(row, tIns.find)
data[tIns.saveTO] = getTextOnly(first.next())
const second = findWithString(row, tIns.find2)
data[tIns.save2TO] = second.next().text()
data[tIns.saveFROM] = getTextOnly(second.next().next())
data[tIns.save2FROM] = row.last().text()
} break;
}
}
}
function removeAfterSpace(fields: string[], data: {[key: string]: string}) {
fields.forEach(field => {
data[field] = data[field].split(' ')[0]
})
}
function convertDate(input: string, date: string, time: string, data: {[key: string]: string} ) {
const startDate = new Date(data[input])
data[date] = startDate.toISOString().slice(0,10).replace(/-/g, '')
data[time] = startDate.toISOString().slice(11,16).replace(/:/g, '')
}
function replaceØ(fields: string[], data: {[key: string]: string}) {
fields.forEach(field => {
data[field] = data[field].replaceAll('Ø','0')
})
}
function convertDecodedDataToRecord(data: {[key: string]: string}) {
//Remove unit from freq and pwr
removeAfterSpace(['freq', 'freq_rx', 'pwr', 'tx_pwr'], data)
delete data['distance'];
delete data['pwr'];
//Covert dates and add time property
convertDate('qso_date', 'qso_date', 'time_on', data)
convertDate('qso_date_off', 'qso_date_off', 'time_off', data)
//Replace Ø with normal 0
replaceØ(['call', 'station_callsign'], data)
//Set qsl_rcvd
if(data['qsl_rcvd'] == 'no') data['qsl_rcvd'] = 'n'
else data['qsl_rcvd'] = 'y'
return data
}
const response = await prompts([
{
type: 'text',
name: 'username',
message: 'Your QRZ username'
},
{
type: 'password',
name: 'password',
message: 'Your QRZ password'
}
]);
//Login
await client.post(loginURL, new URLSearchParams({
username: response.username,
password: response.password
}))
//Find all logbooks
let $ = load((await client.post(logBookURL)).data)
const bookIds = $("option[id^='booksel']").map(function() {
return $(this).attr('id').replace('booksel', '')
}).toArray()
console.log(`Found ${bookIds.length} logbooks.`)
//Convert each logbook to ADIF
bookIds.forEach(async bookId => {
$ = load((await client.post(logBookURL, new URLSearchParams({bookid: bookId}))).data)
const qsoNumber = Number($("input[id='logcount']").val())
console.log(`Found ${qsoNumber} qsos in logbook ${bookId}`);
const records = []
for(let i = 0; i < qsoNumber; i++) {
console.log(`Fetching qso number ${i}`);
$ = load((await client.post(logBookURL, new URLSearchParams({
op: 'show',
bookid: bookId,
logpos: i.toString()
}))).data)
const decodedData = {}
$(".recordTable tr").each(function(i: number) {
if(!rowDecoding[i]) return
decodeRow($(this).children(), i, decodedData)
})
if(Object.keys(decodedData).length === 0) {
console.log(`Couldnt decode QSO number ${i} data. Aborting this logbook decode.`)
return
}
records.push(convertDecodedDataToRecord(decodedData))
}
fs.promises.writeFile(`${bookId}.adi`, AdifFormatter.formatAdi({records: records}))
console.log(`Wrote adi file for logbook ${bookId}`)
})
type singleType = {type: string, find: string, save: string};
type doubleType = {type: string, find: string, saveTO: string, saveFROM: string};
type quadType = {type: string, find: string, saveTO: string, saveFROM: string, find2: string, save2TO: string, save2FROM: string};