-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathyandexapi.js
More file actions
332 lines (286 loc) · 11.5 KB
/
Copy pathyandexapi.js
File metadata and controls
332 lines (286 loc) · 11.5 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
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
const axios = require("axios");
const fs = require("fs");
const path = require("path");
const { defaultDate, binarySearchPositiveMinIdx, getTimeDifferenceFromNow } = require("./utils")
const { Storage } = require("./dynamodb.js");
// const logger = require('pino')()
const hint_level = {
"STARTS_WITH": "STARTS_WITH",
"INCLUDES_STR": "INCLUDES_STR"
}
const config = {
// Список напралений, которые связаны с Москвой
directions_of_interest: [
"Савёловское",
"Московское", // Казанское
"Казанское",
"Горьковское",
"Павелецкое",
"Ярославское",
"МЦД-2",
"Киевское",
"Курское",
"Рижское",
"Белорусское",
"МЦК: Московское центральное кольцо",
"Ленинградское",
"Московский монорельс",
"МЦД-1"
],
// Файл, где хранится обработанный объект всех станций соответствующих напрлений
// cache_stations_filename: "res/yandex-stations.json",
cache_stations_filename: "res/yandex-stations-compressed.json",
// Интересующая страна в Yandex.API
yandex_countryName: "Россия",
// Интересующий регион в Yandex.API
yandex_region: "Москва и Московская область",
// Уровень пользовательских подсказок:
// STARTS_WITH - предлагаются только те станции, которые начинаются со строки, введенной пользователем
// INCLUDES_STR - предлагаются те станции, которые включают в себя строку, введенную пользователем (более требователен к ресурсам)
yandex_hint_level: hint_level.INCLUDES_STR
};
class YandexTrainsAPI {
/**
* @constructor
*/
constructor(yandextoken) {
this.token = yandextoken;
this.cacheStationsFilename = config.cache_stations_filename;
}
/**
* @access public
*
* TODO: use more efficient structure for cached stations instead of array (use hashmap)
*/
getHint(unfinishedStationName) {
// list of stations that starts with `unfinishedStationName`
unfinishedStationName = unfinishedStationName.trim().toLowerCase();
if (!this.stationsMap) this.fetchCachedStations();
let choices = [];
Object.keys(this.stationsMap).forEach(station => {
switch (config.yandex_hint_level) {
case hint_level.INCLUDES_STR:
if (station.toLowerCase().includes(unfinishedStationName)) {
choices.push(this.stationsMap[station]);
}
break;
case hint_level.STARTS_WITH:
if (station.toLowerCase().startsWith(unfinishedStationName)) {
choices.push(this.stationsMap[station]);
}
}
});
return choices;
};
/**
* @access protected
*/
fetchCachedStations() {
this.stationsMap = JSON.parse(
fs.readFileSync(path.resolve(__dirname, config.cache_stations_filename))
);
};
/**
* @access public
* @param {string} stationName - Station name (case insensitive)
*/
getStationObject(stationName) {
if (!this.stationsMap) this.fetchCachedStations();
if (!stationName) return undefined;
return this.stationsMap[stationName.toLowerCase()];
}
/**
* @access public
* @param {string} id - Station id (yandex format)
*/
getStationObjectByID(id) {
if (!this.stationsMap) this.fetchCachedStations();
if (!id) return undefined;
return Object.values(this.stationsMap).find(item => item.id === id);
}
/**
* @access public
*
* @param {string} departure - Departure station (case insensitive)
* @param {string} destination - Destination station (case insensitive)
* @param {Object} [extra] - Extra parameters
* @param {string} [extra.schedule_items=1] - number of schedule items from now, may be less if number of trains is less for specified date
* @param {string} [extra.date=Utils.defaultDate()] - specify date of schedule, by default current date specified in Utils.defaultDate()
*/
async getSchedule(departure, destination, extra = {}) {
const depObj = this.getStationObject(departure);
const destObj = this.getStationObject(destination);
return await this.getScheduleByID(depObj.id, destObj.id, extra)
}
/**
* @access public
*
* @param {string} departureId - Departure station id (in yandex database format: ex. s9600811)
* @param {string} destinationId - Destination station id (in yandex database format: ex. s9600811)
* @param {Object} [extra] - Extra parameters
* @param {string} [extra.schedule_items=1] - number of schedule items from now, may be less if number of trains is less for specified date
* @param {string} [extra.date=Utils.defaultDate()] - specify date of schedule, by default current date specified in Utils.defaultDate()
*/
async getScheduleByID(departureId, destinationId, extra = {}) {
extra = {
schedule_items: 1,
...extra
}
const depObj = { id: departureId }
const destObj = { id: destinationId }
let schedule;
try {
schedule = await this.requestSchedule(depObj, destObj, extra);
}
catch (err) {
// console.debug("Path doesn't exist or " + err.message);
schedule = [];
}
let nearestScheduleIdx = binarySearchPositiveMinIdx(schedule, scheduleItem => getTimeDifferenceFromNow(scheduleItem.departure));
if (nearestScheduleIdx === -1) {
return [];
}
let result = [];
let newIndex;
for (let i = 0; i < extra.schedule_items; i++) {
newIndex = i + nearestScheduleIdx;
if (newIndex >= schedule.length) {
break;
}
result.push(schedule[newIndex]);
}
return result;
}
/**
* @access protected
*
* Requesting schedule with complex logic of checking cache and if it doesn't exist it requests yandex api
* @param {Object} departureObj - Departure station object returned by getStationObject
* @param {Object} destinationObj - Destination station object returned by getStationObject
* @param {Object} [extra] - Extra parameters
* @param {string} [extra.date=Utils.defaultDate()] - specify date of schedule, by default current date specified in Utils.defaultDate()
* @param {boolean} [extra.allow_database_query=true] - allow to check for cache object (without requesting API)
* @param {boolean} [extra.allow_api_query=true] - allow to request API (without checking cache)
* @param {boolean} [extra.allow_cache_write=true] - allow to write API responce to cache
*/
async requestSchedule(departureObj, destinationObj, extra = {}) {
if (!departureObj || !destinationObj) {
return [];
}
extra = {
date: defaultDate(),
allow_database_query: true,
allow_api_query: true,
allow_cache_write: true,
...extra
}
const storage = new Storage();
const writeCache = async(api) => {
if (!extra.allow_cache_write) return false;
// console.log("API query requested to be cached")
return storage.write(extra.date, departureObj.id, destinationObj.id, api);
}
const getCache = async() => {
if (!extra.allow_database_query) throw new Error("Cache query is not allowed");
// console.log("Database query requested");
let cache = await storage.get(extra.date, departureObj.id, destinationObj.id);
let res = cache.Item.Payload;
if (res.length === 0) {
throw Error("Empty cache");
}
return res;
}
const getAPI = () => {
if (!extra.allow_api_query) throw new Error("API query not allowed");
// console.log("API query requested")
return this.requestEntireScheduleFromYandexAPI(departureObj.id, destinationObj.id, extra)
}
let cache;
let api;
try {
cache = await getCache();
// console.log("Cache retrieved")
}
catch (err) {
api = await getAPI();
// console.log("API retrieved")
writeCache(api);
}
if (cache !== undefined && cache.length !== 0) {
return cache;
}
else if (api !== undefined && api.length !== 0) {
return api;
}
else {
throw Error(`No data retrieved with specified extra parameters: ${JSON.stringify(extra)}`)
}
}
/**
* @access protected
*/
async requestEntireScheduleFromYandexAPI(departureObjectId, destinationObjectId, extra = {}) {
const simplifySegments = (segments, arrayToPush) => {
segments.forEach(segment => {
arrayToPush.push({
arrival: segment.arrival,
departure: segment.departure,
duration: segment.duration,
threadName: segment.thread.title
})
})
}
let recievedData = [];
let total = 1,
limit = 0,
offset = 0;
while ((offset + limit) <= total) {
offset += limit;
let page = await this.requestSchedulePageFromYandexAPI(departureObjectId, destinationObjectId, { ...extra, offset: offset });
simplifySegments(page.data.segments, recievedData);
({ total, limit, offset } = page.data.pagination);
// console.log(`Page: ${total}: ${offset}-${limit}`);
}
return recievedData;
}
/**
* @access protected
*/
requestSchedulePageFromYandexAPI(departureObjectId, destinationObjectId, extra = {}) {
let date = extra.date || defaultDate();
let offset = extra.offset || 0;
let res = axios.get(
"https://api.rasp.yandex.net/v3.0/search/", {
params: {
apikey: this.token,
format: "json",
lang: "ru_RU",
from: departureObjectId,
to: destinationObjectId,
date: date,
transfers: 'false',
offset: offset
}
}
)
return res;
}
deleteScheduleFromCache(departureObjectId, destinationObjectId, date = defaultDate()) {
const storage = new Storage();
return storage.delete(date, departureObjectId, destinationObjectId);
}
/**
* @access private
*/
testYandexAPIConnection() {
// console.log(this.token);
let res = axios.get("https://api.rasp.yandex.net/v3.0/copyright/", {
params: {
apikey: this.token,
format: "json"
}
});
return res;
}
}
module.exports.YandexTrainsAPI = YandexTrainsAPI;