market-data-tradingview-ws 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +90 -68
- package/lib/tradingView.js +17 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -13,10 +13,18 @@ const periods = {
|
|
|
13
13
|
3600: '60',
|
|
14
14
|
14400: '240',
|
|
15
15
|
86400: '1D',
|
|
16
|
+
604800: '1W',
|
|
17
|
+
2592000: '1M',
|
|
16
18
|
};
|
|
17
19
|
|
|
20
|
+
const extractJSON = (str) => {
|
|
21
|
+
const match = str.match(/\{.*?\}/s);
|
|
22
|
+
return match ? JSON.parse(match[0]) : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let ws = null;
|
|
26
|
+
|
|
18
27
|
module.exports = class Client {
|
|
19
|
-
#ws;
|
|
20
28
|
#charts = {
|
|
21
29
|
values: {},
|
|
22
30
|
set({ key, data }) {
|
|
@@ -29,6 +37,7 @@ module.exports = class Client {
|
|
|
29
37
|
frame: data.frame,
|
|
30
38
|
i: data.i,
|
|
31
39
|
deleted: data.deleted || false,
|
|
40
|
+
limit: data.limit || 1500,
|
|
32
41
|
};
|
|
33
42
|
}
|
|
34
43
|
},
|
|
@@ -84,17 +93,29 @@ module.exports = class Client {
|
|
|
84
93
|
if (index !== -1) arr.splice(index, 1);
|
|
85
94
|
},
|
|
86
95
|
};
|
|
96
|
+
#symbols = {
|
|
97
|
+
values: {},
|
|
98
|
+
max: 1,
|
|
99
|
+
getSymbol({ key, symbol }) {
|
|
100
|
+
// let data = this.values[symbol];
|
|
101
|
+
// if (data === undefined) data = this.set({ key, symbol });
|
|
102
|
+
// return data;
|
|
103
|
+
const symbolKey = 'sds_sym_' + this.max++;
|
|
104
|
+
ws.send(tv.resolveSymbol({ chartSession: key, symbolKey, symbol }));
|
|
105
|
+
this.values[symbol] = { key: symbolKey };
|
|
106
|
+
return this.values[symbol];
|
|
107
|
+
},
|
|
108
|
+
};
|
|
87
109
|
|
|
88
|
-
// #resolved;
|
|
89
110
|
#token = null;
|
|
90
|
-
#maxSymbolKey = 1;
|
|
91
111
|
|
|
92
112
|
constructor() {
|
|
93
113
|
// this.#resolved = new Map();
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
close() {
|
|
97
|
-
|
|
116
|
+
async close() {
|
|
117
|
+
// token delete !!!
|
|
118
|
+
ws.close();
|
|
98
119
|
}
|
|
99
120
|
|
|
100
121
|
getAll() {
|
|
@@ -130,23 +151,31 @@ module.exports = class Client {
|
|
|
130
151
|
});
|
|
131
152
|
}
|
|
132
153
|
|
|
133
|
-
async connect({ url, options, login, pass, result }) {
|
|
134
|
-
this.#token = await this.getToken({ login, pass });
|
|
135
|
-
|
|
154
|
+
async connect({ url, options, login, pass, result, token }) {
|
|
155
|
+
this.#token = token || await this.getToken({ login, pass });
|
|
156
|
+
|
|
136
157
|
if (this.#token.length > 10) {
|
|
137
158
|
return new Promise((resolv, reject) => {
|
|
138
|
-
|
|
159
|
+
url += '?from=chart%2FKq8EfKQU%2F';
|
|
160
|
+
const date = new Date();
|
|
161
|
+
url += '&date=' + date.getFullYear() + '_' + String(date.getMonth() + 1).padStart(2, '0') + '_' + String(date.getDate()).padStart(2, '0') + '-11_25';
|
|
162
|
+
url += '&type=chart';
|
|
163
|
+
ws = new WebSocket(url, options);
|
|
164
|
+
|
|
165
|
+
const originalSend = ws.send;
|
|
166
|
+
ws.send = function(data) {
|
|
167
|
+
// console.log('Message sent:', data);
|
|
168
|
+
originalSend.apply(ws, arguments);
|
|
169
|
+
};
|
|
139
170
|
|
|
140
|
-
|
|
171
|
+
ws.onclose = () => console.log('disconnected');
|
|
141
172
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
173
|
+
ws.onerror = (error) => reject(console.error('error: ', error));
|
|
174
|
+
ws.onopen = () => {
|
|
175
|
+
ws.send(this.auth(this.#token));
|
|
176
|
+
ws.send(tv.setLocaleRu());
|
|
146
177
|
|
|
147
|
-
// const chartKey = this.createChartSession();
|
|
148
178
|
const quoteKey = this.createQuoteSmallSession();
|
|
149
|
-
// this.addQuoteSymbols({ symbols: ['NASDAQ:PYPL'] });
|
|
150
179
|
|
|
151
180
|
this.messageListner({ result });
|
|
152
181
|
resolv('open');
|
|
@@ -159,24 +188,24 @@ module.exports = class Client {
|
|
|
159
188
|
createQuoteSmallSession() {
|
|
160
189
|
const key = tv.createSession({ startsWith: 'qs_' });
|
|
161
190
|
this.#quotes.set({ key, data: { id: key, type: 'small', symbols: [] } });
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
//
|
|
191
|
+
ws.send(tv.quoteCreateSession({ session: key }));
|
|
192
|
+
ws.send(tv.setFieldsSmall({ session: key }));
|
|
193
|
+
// ws.send(tv.setFields({ session: key }));
|
|
165
194
|
return key;
|
|
166
195
|
}
|
|
167
196
|
|
|
168
197
|
createQuoteFullSession() {
|
|
169
198
|
const key = tv.createSession({ startsWith: 'qs_' });
|
|
170
199
|
this.#quotes.set({ key, data: { id: key, type: 'full', symbols: [] } });
|
|
171
|
-
|
|
200
|
+
ws.send(tv.quoteCreateSession({ session: key }));
|
|
172
201
|
return key;
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
createChartSession() {
|
|
176
205
|
const key = tv.createSession({ startsWith: 'cs_' });
|
|
177
206
|
this.#charts.set({ key, data: { id: key, symbol: '', period: 0, symbolKey: '', frame: '', i: '', deleted: false } });
|
|
178
|
-
|
|
179
|
-
|
|
207
|
+
ws.send(tv.chartCreateSession({ chartSession: key }));
|
|
208
|
+
ws.send(tv.switchTimezone({ chartSession: key, tz: 'Etc/UTC' }));
|
|
180
209
|
return key;
|
|
181
210
|
}
|
|
182
211
|
|
|
@@ -189,9 +218,8 @@ module.exports = class Client {
|
|
|
189
218
|
result.exist.push(symbol);
|
|
190
219
|
} else {
|
|
191
220
|
key = this.#quotes.add({ symbol });
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// this.#ws.send(tv.quoteFastSymbol({ session: key, symbol }));
|
|
221
|
+
ws.send(tv.quoteAddSymbol({ session: key, symbol }));
|
|
222
|
+
// ws.send(tv.quoteFastSymbol({ session: key, symbol }));
|
|
195
223
|
result.add.push(symbol);
|
|
196
224
|
}
|
|
197
225
|
});
|
|
@@ -202,39 +230,31 @@ module.exports = class Client {
|
|
|
202
230
|
const key = this.#quotes.getBySymbol({ symbol });
|
|
203
231
|
if (!key) return 'unlisted';
|
|
204
232
|
this.#quotes.delete({ key, symbol });
|
|
205
|
-
|
|
233
|
+
ws.send(tv.quoteRemoveSymbols({ session: key, symbol }));
|
|
206
234
|
return 'delete';
|
|
207
235
|
}
|
|
208
236
|
|
|
209
|
-
resolveSymbol({ key, symbol }) {
|
|
210
|
-
const symbolKey = 'sds_sym_' + this.#maxSymbolKey++;
|
|
211
|
-
this.#ws.send(tv.resolveSymbol({ chartSession: key, symbolKey, symbol }));
|
|
212
|
-
return symbolKey;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
237
|
addChartSymbol({ symbol, period, limit }) {
|
|
216
238
|
let key = this.#charts.getBySymbolPeriod({ symbol, period });
|
|
217
239
|
if (key) {
|
|
218
|
-
console.warn('addChartSymbol exist');
|
|
240
|
+
console.warn('addChartSymbol exist:', key, symbol, period);
|
|
219
241
|
return 'exist';
|
|
220
242
|
}
|
|
221
|
-
|
|
222
243
|
// console.warn('modules addChartSymbol1', symbol, period, limit, key);
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
244
|
+
const deleted = this.#charts.getDeleted();
|
|
245
|
+
if (deleted !== null && deleted.limit === limit) return this.updateChartSymbol({ symbol, period, limit, last: { symbol: deleted.symbol, period: parseInt(deleted.period) }});
|
|
225
246
|
|
|
226
247
|
key = this.createChartSession();
|
|
227
248
|
const data = this.#charts.get({ key });
|
|
228
249
|
|
|
229
|
-
data.symbolKey = this.
|
|
250
|
+
data.symbolKey = this.#symbols.getSymbol({ key, symbol }).key;
|
|
230
251
|
data.symbol = symbol;
|
|
231
252
|
data.period = period;
|
|
232
253
|
data.frame = 'sds_' + Object.keys(this.#charts.values).length;
|
|
233
254
|
data.i = 's' + 1;
|
|
255
|
+
data.limit = limit;
|
|
234
256
|
|
|
235
|
-
|
|
236
|
-
// this.#resolved.set(symbol, { key: data.symbolKey });
|
|
237
|
-
this.#ws.send(
|
|
257
|
+
ws.send(
|
|
238
258
|
tv.createSeries({
|
|
239
259
|
chartSession: key,
|
|
240
260
|
frame: data.frame,
|
|
@@ -244,49 +264,56 @@ module.exports = class Client {
|
|
|
244
264
|
limit,
|
|
245
265
|
}),
|
|
246
266
|
);
|
|
247
|
-
// console.
|
|
267
|
+
// console.warn('addChartSymbol4', periods[period], data);
|
|
248
268
|
this.#charts.set({ key, data });
|
|
249
|
-
|
|
250
269
|
return 'add';
|
|
251
270
|
}
|
|
252
271
|
|
|
253
272
|
updateChartSymbol({ symbol, period, limit, last }) {
|
|
254
|
-
// period = parseInt(period);
|
|
255
|
-
// last.period = parseInt(last.period);
|
|
256
273
|
const key = this.#charts.getBySymbolPeriod({ symbol: last.symbol, period: last.period });
|
|
257
274
|
const data = this.#charts.get({ key });
|
|
258
275
|
|
|
259
276
|
// console.warn('modules updateChartSymbol 1: ', symbol, period, limit, last, data);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
277
|
+
// const alreadyKey = this.#charts.getBySymbolPeriod({ symbol, period })
|
|
278
|
+
// if (alreadyKey) {
|
|
279
|
+
// // console.warn('updateChartSymbol exist: ', alreadyKey);
|
|
280
|
+
// this.#charts.delete({ key });
|
|
281
|
+
// // console.warn('updateChartSymbol exist');
|
|
282
|
+
// return 'exist';
|
|
283
|
+
// }
|
|
268
284
|
|
|
269
|
-
data.symbolKey = this.
|
|
285
|
+
data.symbolKey = this.#symbols.getSymbol({ key, symbol }).key;
|
|
270
286
|
data.symbol = symbol;
|
|
271
287
|
data.period = period;
|
|
272
288
|
data.i = 's' + (parseInt(data.i.replace(/[^0-9]/g, '')) + 1);
|
|
273
|
-
|
|
289
|
+
|
|
290
|
+
ws.send(
|
|
274
291
|
tv.modifySeries({
|
|
275
292
|
chartSession: key,
|
|
276
293
|
frame: data.frame,
|
|
277
294
|
increment: data.i,
|
|
278
295
|
symbolKey: data.symbolKey,
|
|
279
296
|
interval: periods[period],
|
|
280
|
-
limit,
|
|
281
297
|
}),
|
|
282
298
|
);
|
|
283
299
|
this.#charts.set({ key, data });
|
|
300
|
+
if (data.limit < limit) {
|
|
301
|
+
ws.send(tv.moreData({ chartSession: key, frame: data.frame, add: limit - data.limit }));
|
|
302
|
+
data.limit = limit;
|
|
303
|
+
}
|
|
284
304
|
return 'update';
|
|
285
305
|
}
|
|
286
306
|
|
|
307
|
+
deleteChartSymbol({ symbol, period }) {
|
|
308
|
+
let key = this.#charts.getBySymbolPeriod({ symbol, period });
|
|
309
|
+
if (!key) return void console.warn('deleteChartSymbol empty key');
|
|
310
|
+
this.#charts.delete({ key });
|
|
311
|
+
return void console.log('deleteChartSymbol: ', key);
|
|
312
|
+
}
|
|
313
|
+
|
|
287
314
|
messageListner({ result }) {
|
|
288
|
-
|
|
289
|
-
if (
|
|
315
|
+
ws.on('message', (raw) => {
|
|
316
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
290
317
|
|
|
291
318
|
// console.log('raw: ', JSON.stringify(raw.toString()));
|
|
292
319
|
let session = '';
|
|
@@ -296,19 +323,14 @@ module.exports = class Client {
|
|
|
296
323
|
if (packet === undefined) {
|
|
297
324
|
console.warn('undefined: ', raw.toString());
|
|
298
325
|
} else {
|
|
299
|
-
// eslint-disable-next-line no-lonely-if
|
|
300
326
|
if (packet.type === 'ping') {
|
|
301
327
|
// console.info('pong', packet);
|
|
302
|
-
|
|
303
|
-
// } else if (packet.type === 'protocol_error') {
|
|
304
|
-
// console.warn('protocol_error', packet);
|
|
305
|
-
// this.close();
|
|
328
|
+
ws.send(tv.createPong({ value: packet.data }));
|
|
306
329
|
} else if (packet.type === 'qsd') {
|
|
307
330
|
send = packet.data[1].v;
|
|
308
331
|
name = send.bid === undefined && send.ask === undefined ? 'data' : 'levelI';
|
|
309
|
-
// console.
|
|
310
|
-
|
|
311
|
-
send.symbol = JSON.parse(packet.data[1].n.substring(1)).symbol;
|
|
332
|
+
// console.log(packet.data[1].n);
|
|
333
|
+
send.symbol =(extractJSON(packet.data[1].n) || {symbol: packet.data[1].n}).symbol;
|
|
312
334
|
result(name, send);
|
|
313
335
|
} else if (['du', 'timescale_update'].includes(packet.type)) {
|
|
314
336
|
session = this.#charts.get({ key: packet.data[0] });
|
|
@@ -335,12 +357,12 @@ module.exports = class Client {
|
|
|
335
357
|
} else if (['series', 'symbol', 'quote', 'session'].includes(packet.type)) {
|
|
336
358
|
// console.info(packet);
|
|
337
359
|
} else {
|
|
338
|
-
console.
|
|
360
|
+
// console.error('error', raw.toString());
|
|
339
361
|
result('error', raw.toString())
|
|
340
362
|
}
|
|
341
363
|
}
|
|
342
|
-
name = null;
|
|
343
|
-
send = null;
|
|
364
|
+
// name = null;
|
|
365
|
+
// send = null;
|
|
344
366
|
});
|
|
345
367
|
});
|
|
346
368
|
}
|
package/lib/tradingView.js
CHANGED
|
@@ -35,8 +35,7 @@ class TradingView {
|
|
|
35
35
|
// }
|
|
36
36
|
|
|
37
37
|
// await utils.pause();
|
|
38
|
-
|
|
39
|
-
return 'eyJhbGciOiJSUzUxMiIsImtpZCI6IkdaeFUiLCJ0eXAiOiJKV1QifQ.eyJ1c2VyX2lkIjoxMjI1ODcxMiwiZXhwIjoxNzA1OTE3OTE1LCJpYXQiOjE3MDU5MDM1MTUsInBsYW4iOiJwcm9fcHJlbWl1bSIsImV4dF9ob3VycyI6MSwicGVybSI6IiIsInN0dWR5X3Blcm0iOiJ0di1wcm9zdHVkaWVzLHR2LWNoYXJ0X3BhdHRlcm5zLHR2LXZvbHVtZWJ5cHJpY2UsdHYtY2hhcnRwYXR0ZXJucyIsIm1heF9zdHVkaWVzIjoyNSwibWF4X2Z1bmRhbWVudGFscyI6MTAsIm1heF9jaGFydHMiOjgsIm1heF9hY3RpdmVfYWxlcnRzIjo0MDAsIm1heF9zdHVkeV9vbl9zdHVkeSI6MjQsIm1heF9vdmVyYWxsX2FsZXJ0cyI6MjAwMCwibWF4X2FjdGl2ZV9wcmltaXRpdmVfYWxlcnRzIjo0MDAsIm1heF9hY3RpdmVfY29tcGxleF9hbGVydHMiOjQwMCwibWF4X2Nvbm5lY3Rpb25zIjo1MH0.Zoe1DAU3Fn2XpNNvI7uYK0JGHEsNs_OjTqBsnj8vsqWWeDDaOFPVO5m_DLuaUaSFZ4w3diQtgqfeovyPzzUCFDooGEF9FRypY9-zTjGbV0pbQajx-a8X3j79qxvLWe7VYg7cx5RQJGBoN7CGIPzrdpvNvVEYGb_QqXOPy_-F1BE';
|
|
38
|
+
return 'eyJhbGciOiJSUzUxMiIsImtpZCI6IkdaeFUiLCJ0eXAiOiJKV1QifQ.eyJ1c2VyX2lkIjoxMjI1ODcxMiwiZXhwIjoxNzI0OTI1MjkyLCJpYXQiOjE3MjQ5MTA4OTIsInBsYW4iOiJwcm9fcHJlbWl1bSIsImV4dF9ob3VycyI6MSwicGVybSI6InVzLXN0b2NrcyxuYXNkYXFfZ2lkcyxvdGMsbnlzZSxuYXNkYXEsYW1leCIsInN0dWR5X3Blcm0iOiJ0di1jaGFydHBhdHRlcm5zLHR2LXByb3N0dWRpZXMsdHYtY2hhcnRfcGF0dGVybnMsdHYtdm9sdW1lYnlwcmljZSIsIm1heF9zdHVkaWVzIjoyNSwibWF4X2Z1bmRhbWVudGFscyI6MTAsIm1heF9jaGFydHMiOjgsIm1heF9hY3RpdmVfYWxlcnRzIjo0MDAsIm1heF9zdHVkeV9vbl9zdHVkeSI6MjQsImZpZWxkc19wZXJtaXNzaW9ucyI6WyJyZWZib25kcyJdLCJtYXhfb3ZlcmFsbF9hbGVydHMiOjIwMDAsIm1heF9hY3RpdmVfcHJpbWl0aXZlX2FsZXJ0cyI6NDAwLCJtYXhfYWN0aXZlX2NvbXBsZXhfYWxlcnRzIjo0MDAsIm1heF9jb25uZWN0aW9ucyI6NTB9.UT2AdPBEAWk6HJEa6isX4gjVqlfThn8bgOVl3Xa36U36Qifg5xXz9Va8q7DWSY3Smaap2wX4cBF4UQXv1t66Q0TQvFudLuvA2dnetBAHYZSOJIi2PGdG1KuMaCRshUuuH9mxgw_xPGITAuQsbBPZJLTOxq5pQN05pZnedC7SaMs';
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
async getSymbol({ symbol, type }) {
|
|
@@ -60,9 +59,7 @@ class TradingView {
|
|
|
60
59
|
|
|
61
60
|
createSession({ startsWith }) {
|
|
62
61
|
let session = startsWith;
|
|
63
|
-
while (session.length < 12)
|
|
64
|
-
session += letters[Math.floor(Math.random() * letters.length)];
|
|
65
|
-
}
|
|
62
|
+
while (session.length < 12) session += letters[Math.floor(Math.random() * letters.length)];
|
|
66
63
|
return session;
|
|
67
64
|
}
|
|
68
65
|
|
|
@@ -229,10 +226,11 @@ class TradingView {
|
|
|
229
226
|
}
|
|
230
227
|
|
|
231
228
|
quoteAddSymbol({ session, symbol }) {
|
|
232
|
-
|
|
229
|
+
const symbolString = symbol.split(':')[0] === 'BINANCE' ? symbol : '={"adjustment":"splits","currency-id":"USD","symbol":"' + symbol + '"}';
|
|
230
|
+
// console.log('quoteAddSymbol', session, symbol, symbolString);
|
|
233
231
|
// return this.formatWSPacket({ packet: { m: 'quote_add_symbols', p: [session, symbol] } });
|
|
234
232
|
return this.formatWSPacket({
|
|
235
|
-
packet: { m: 'quote_add_symbols', p: [session,
|
|
233
|
+
packet: { m: 'quote_add_symbols', p: [session, symbolString] }
|
|
236
234
|
});
|
|
237
235
|
}
|
|
238
236
|
|
|
@@ -269,10 +267,10 @@ class TradingView {
|
|
|
269
267
|
// symbolKey - id запрошенного тиккера
|
|
270
268
|
// frame - рамка или окно для данной серии
|
|
271
269
|
// increment - инкрементируемый номер изменений запрашиваемого графика в данном frame (таймефрейм)
|
|
272
|
-
createSeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey,
|
|
270
|
+
createSeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, period = '60', limit = 1000 }) {
|
|
273
271
|
// console.warn('createSeries', chartSession, frame, increment, symbolKey, interval, limit);
|
|
274
272
|
return this.formatWSPacket({
|
|
275
|
-
packet: { m: 'create_series', p: [chartSession, frame, increment, symbolKey,
|
|
273
|
+
packet: { m: 'create_series', p: [chartSession, frame, increment, symbolKey, period, limit] },
|
|
276
274
|
});
|
|
277
275
|
}
|
|
278
276
|
|
|
@@ -287,13 +285,22 @@ class TradingView {
|
|
|
287
285
|
return this.formatWSPacket({ packet: { m: 'remove_study', p: [ session, st ] } });
|
|
288
286
|
}
|
|
289
287
|
|
|
290
|
-
modifySeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, interval = '60'
|
|
288
|
+
modifySeries({ chartSession, frame = 'sds_1', increment = 's1', symbolKey, interval = '60' }) {
|
|
291
289
|
// console.warn('modify_series', chartSession, frame, increment, symbolKey, interval, limit);
|
|
292
290
|
return this.formatWSPacket({
|
|
293
291
|
packet: { m: 'modify_series', p: [chartSession, frame, increment, symbolKey, interval] },
|
|
294
292
|
});
|
|
295
293
|
}
|
|
296
294
|
|
|
295
|
+
moreData({ chartSession, frame, add }) {
|
|
296
|
+
return this.formatWSPacket({ packet: { m: 'request_more_data', p: [chartSession, frame, add] } });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
moreTickmark({chartSession, frame, add}) {
|
|
300
|
+
// "m":"request_more_tickmarks","p":["cs_lslcBHYaqGTT","sds_1",10]
|
|
301
|
+
return this.formatWSPacket({ packet: { m: 'request_more_tickmarks', p: [chartSession, frame, add] } });
|
|
302
|
+
}
|
|
303
|
+
|
|
297
304
|
createPong({ value }) {
|
|
298
305
|
return this.formatWSPacket({ packet: '~h~' + value });
|
|
299
306
|
}
|