stfca 1.0.26 → 1.1.27
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/checkUpdate.js +53 -49
- package/index.js +91 -15
- package/package.json +7 -8
- package/src/listenMqtt.js +394 -393
- package/src/sendMessage.js +292 -278
- package/src/sendMessageMqtt.js +0 -1
- package/src/uploadAttachment.js +99 -77
- package/utils.js +75 -13
package/src/sendMessage.js
CHANGED
|
@@ -1,285 +1,299 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const utils = require('../utils');
|
|
4
|
-
|
|
4
|
+
const log = require('npmlog');
|
|
5
5
|
|
|
6
6
|
const allowedProperties = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
attachment: true,
|
|
8
|
+
url: true,
|
|
9
|
+
sticker: true,
|
|
10
|
+
emoji: true,
|
|
11
|
+
emojiSize: true,
|
|
12
|
+
body: true,
|
|
13
|
+
mentions: true,
|
|
14
|
+
location: true,
|
|
15
|
+
replyToMessage: true,
|
|
16
|
+
forwardAttachmentIds: true,
|
|
15
17
|
};
|
|
16
18
|
|
|
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
|
-
};
|
|
19
|
+
const EMOJI_SIZES = { small: 1, medium: 2, large: 3 };
|
|
20
|
+
|
|
21
|
+
function toEmojiSize(size) {
|
|
22
|
+
if (typeof size === "number" && !isNaN(size)) return Math.min(3, Math.max(1, size));
|
|
23
|
+
if (typeof size === "string" && size in EMOJI_SIZES) return EMOJI_SIZES[size];
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasLinks(text) {
|
|
28
|
+
return /(https?:\/\/|www\.|t\.me\/|fb\.me\/|youtu\.be\/|facebook\.com\/|youtube\.com\/)/i.test(text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildMentionData(msg, baseBody) {
|
|
32
|
+
if (!Array.isArray(msg.mentions) || !msg.mentions.length) return null;
|
|
33
|
+
var ids = [], offsets = [], lengths = [], types = [];
|
|
34
|
+
var cursor = 0;
|
|
35
|
+
for (var i = 0; i < msg.mentions.length; i++) {
|
|
36
|
+
var mention = msg.mentions[i];
|
|
37
|
+
var rawTag = String(mention.tag || "");
|
|
38
|
+
var displayName = rawTag.replace(/^@+/, "");
|
|
39
|
+
var start = Number.isInteger(mention.fromIndex) ? mention.fromIndex : cursor;
|
|
40
|
+
var index = baseBody.indexOf(rawTag, start);
|
|
41
|
+
var adjustment = 0;
|
|
42
|
+
if (index === -1) {
|
|
43
|
+
index = baseBody.indexOf(displayName, start);
|
|
44
|
+
} else {
|
|
45
|
+
adjustment = rawTag.length - displayName.length;
|
|
46
|
+
}
|
|
47
|
+
if (index < 0) { index = 0; adjustment = 0; }
|
|
48
|
+
var offset = index + adjustment;
|
|
49
|
+
ids.push(String(mention.id || 0));
|
|
50
|
+
offsets.push(offset);
|
|
51
|
+
lengths.push(displayName.length);
|
|
52
|
+
types.push("p");
|
|
53
|
+
cursor = offset + displayName.length;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
mention_ids: ids.join(","),
|
|
57
|
+
mention_offsets: offsets.join(","),
|
|
58
|
+
mention_lengths: lengths.join(","),
|
|
59
|
+
mention_types: types.join(",")
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractIdsFromPayload(payload) {
|
|
64
|
+
var messageID = null, threadID = null;
|
|
65
|
+
function walk(node) {
|
|
66
|
+
if (!Array.isArray(node)) return;
|
|
67
|
+
if (node[0] === 5 && (node[1] === "replaceOptimsiticMessage" || node[1] === "replaceOptimisticMessage")) {
|
|
68
|
+
messageID = String(node[3]);
|
|
69
|
+
}
|
|
70
|
+
if (node[0] === 5 && node[1] === "writeCTAIdToThreadsTable") {
|
|
71
|
+
var candidate = node[2];
|
|
72
|
+
if (Array.isArray(candidate) && candidate[0] === 19) threadID = String(candidate[1]);
|
|
73
|
+
}
|
|
74
|
+
for (var i = 0; i < node.length; i++) walk(node[i]);
|
|
75
|
+
}
|
|
76
|
+
try { walk(payload && payload.step); } catch (_) { }
|
|
77
|
+
return { threadID: threadID, messageID: messageID };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function publishLsRequestWithAck(mqttClient, content, requestId, timeout) {
|
|
81
|
+
timeout = timeout || 15000;
|
|
82
|
+
return new Promise(function (resolve, reject) {
|
|
83
|
+
var timer = setTimeout(function () {
|
|
84
|
+
mqttClient.removeListener('message', onMessage);
|
|
85
|
+
reject(new Error('MQTT sendMessage timed out'));
|
|
86
|
+
}, timeout);
|
|
87
|
+
|
|
88
|
+
function onMessage(topic, message) {
|
|
89
|
+
if (topic !== '/ls_resp') return;
|
|
90
|
+
try {
|
|
91
|
+
var data = JSON.parse(message.toString());
|
|
92
|
+
if (String(data.request_id) === String(requestId)) {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
mqttClient.removeListener('message', onMessage);
|
|
95
|
+
var extracted = extractIdsFromPayload(
|
|
96
|
+
data.payload ? JSON.parse(data.payload) : {}
|
|
97
|
+
);
|
|
98
|
+
resolve({
|
|
99
|
+
threadID: extracted.threadID,
|
|
100
|
+
messageID: extracted.messageID
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} catch (_) { }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
mqttClient.on('message', onMessage);
|
|
107
|
+
mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1 }, function (err) {
|
|
108
|
+
if (err) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
mqttClient.removeListener('message', onMessage);
|
|
111
|
+
reject(err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = function (defaultFuncs, api, ctx) {
|
|
118
|
+
var uploadAttachmentFn = require('./uploadAttachment')(defaultFuncs, api, ctx);
|
|
119
|
+
|
|
120
|
+
async function uploadAttachments(attachments) {
|
|
121
|
+
if (!Array.isArray(attachments)) attachments = [attachments];
|
|
122
|
+
return await uploadAttachmentFn(attachments);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function sendViaMqtt(msg, threadID, replyToMessage) {
|
|
126
|
+
var mqttClient = ctx.mqttClient || global.mqttClient;
|
|
127
|
+
if (!mqttClient) throw new Error('MQTT client not available');
|
|
128
|
+
|
|
129
|
+
var baseBody = msg.body != null ? String(msg.body) : "";
|
|
130
|
+
var requestId = Math.floor(100 + Math.random() * 900);
|
|
131
|
+
var epoch = (BigInt(Date.now()) << 22n).toString();
|
|
132
|
+
|
|
133
|
+
var payload0 = {
|
|
134
|
+
thread_id: String(threadID),
|
|
135
|
+
otid: utils.generateOfflineThreadingID(),
|
|
136
|
+
source: 2097153,
|
|
137
|
+
send_type: 1,
|
|
138
|
+
sync_group: 1,
|
|
139
|
+
mark_thread_read: 1,
|
|
140
|
+
text: baseBody === "" ? null : baseBody,
|
|
141
|
+
initiating_source: 0,
|
|
142
|
+
skip_url_preview_gen: 0,
|
|
143
|
+
text_has_links: hasLinks(baseBody) ? 1 : 0,
|
|
144
|
+
multitab_env: 0,
|
|
145
|
+
metadata_dataclass: JSON.stringify({ media_accessibility_metadata: { alt_text: null } })
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
var mentionData = buildMentionData(msg, baseBody);
|
|
149
|
+
if (mentionData) payload0.mention_data = mentionData;
|
|
150
|
+
|
|
151
|
+
if (msg.sticker) {
|
|
152
|
+
payload0.send_type = 2;
|
|
153
|
+
payload0.sticker_id = msg.sticker;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (msg.emoji) {
|
|
157
|
+
payload0.send_type = 1;
|
|
158
|
+
payload0.text = msg.emoji;
|
|
159
|
+
payload0.hot_emoji_size = toEmojiSize(msg.emojiSize);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (msg.location && msg.location.latitude != null && msg.location.longitude != null) {
|
|
163
|
+
payload0.send_type = 1;
|
|
164
|
+
payload0.location_data = {
|
|
165
|
+
coordinates: { latitude: msg.location.latitude, longitude: msg.location.longitude },
|
|
166
|
+
is_current_location: Boolean(msg.location.current),
|
|
167
|
+
is_live_location: Boolean(msg.location.live)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var effectiveReplyTo = replyToMessage || msg.replyToMessage;
|
|
172
|
+
if (effectiveReplyTo) {
|
|
173
|
+
payload0.reply_metadata = {
|
|
174
|
+
reply_source_id: effectiveReplyTo,
|
|
175
|
+
reply_source_type: 1,
|
|
176
|
+
reply_type: 0
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (msg.attachment) {
|
|
181
|
+
payload0.send_type = 3;
|
|
182
|
+
if (payload0.text === "") payload0.text = null;
|
|
183
|
+
payload0.attachment_fbids = [];
|
|
184
|
+
|
|
185
|
+
var list = Array.isArray(msg.attachment) ? msg.attachment : [msg.attachment];
|
|
186
|
+
var preuploaded = [];
|
|
187
|
+
var toUpload = [];
|
|
188
|
+
|
|
189
|
+
for (var i = 0; i < list.length; i++) {
|
|
190
|
+
var item = list[i];
|
|
191
|
+
if (Array.isArray(item) && item.length >= 2 && typeof item[0] === "string") {
|
|
192
|
+
preuploaded.push(String(item[1]));
|
|
193
|
+
} else if (utils.isReadableStream(item)) {
|
|
194
|
+
toUpload.push(item);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (preuploaded.length) {
|
|
199
|
+
payload0.attachment_fbids = payload0.attachment_fbids.concat(preuploaded);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (Array.isArray(msg.forwardAttachmentIds) && msg.forwardAttachmentIds.length) {
|
|
203
|
+
payload0.attachment_fbids = payload0.attachment_fbids.concat(msg.forwardAttachmentIds.map(String));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (toUpload.length) {
|
|
207
|
+
var uploaded = await uploadAttachments(toUpload);
|
|
208
|
+
for (var f = 0; f < uploaded.length; f++) {
|
|
209
|
+
var key = Object.keys(uploaded[f])[0];
|
|
210
|
+
payload0.attachment_fbids.push(String(uploaded[f][key]));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
var content = {
|
|
216
|
+
app_id: "2220391788200892",
|
|
217
|
+
payload: {
|
|
218
|
+
tasks: [
|
|
219
|
+
{
|
|
220
|
+
label: "46",
|
|
221
|
+
payload: payload0,
|
|
222
|
+
queue_name: String(threadID),
|
|
223
|
+
task_id: 400,
|
|
224
|
+
failure_count: null
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
label: "21",
|
|
228
|
+
payload: {
|
|
229
|
+
thread_id: String(threadID),
|
|
230
|
+
last_read_watermark_ts: Date.now(),
|
|
231
|
+
sync_group: 1
|
|
232
|
+
},
|
|
233
|
+
queue_name: String(threadID),
|
|
234
|
+
task_id: 401,
|
|
235
|
+
failure_count: null
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
epoch_id: epoch,
|
|
239
|
+
version_id: "24804310205905615",
|
|
240
|
+
data_trace_id: "#" + Buffer.from(String(Math.random())).toString("base64").replace(/=+$/, "")
|
|
241
|
+
},
|
|
242
|
+
request_id: requestId,
|
|
243
|
+
type: 3
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
content.payload.tasks = content.payload.tasks.map(function (task) {
|
|
247
|
+
return Object.assign({}, task, { payload: JSON.stringify(task.payload) });
|
|
248
|
+
});
|
|
249
|
+
content.payload = JSON.stringify(content.payload);
|
|
250
|
+
|
|
251
|
+
return await publishLsRequestWithAck(mqttClient, content, requestId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return async function sendMessage(msg, threadID, callback, replyToMessage, isSingleUser) {
|
|
255
|
+
if (typeof callback === "string") {
|
|
256
|
+
isSingleUser = replyToMessage;
|
|
257
|
+
replyToMessage = callback;
|
|
258
|
+
callback = function () { };
|
|
259
|
+
} else if (typeof callback !== "function") {
|
|
260
|
+
callback = function () { };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
var msgType = utils.getType(msg);
|
|
264
|
+
var threadIDType = utils.getType(threadID);
|
|
265
|
+
|
|
266
|
+
if (msgType !== "String" && msgType !== "Object") throw new Error("Message should be of type string or object and not " + msgType + ".");
|
|
267
|
+
if (threadIDType !== "Array" && threadIDType !== "Number" && threadIDType !== "String") throw new Error("ThreadID should be of type number, string, or array and not " + threadIDType + ".");
|
|
268
|
+
if (replyToMessage && utils.getType(replyToMessage) !== "String") throw new Error("replyToMessage should be of type string.");
|
|
269
|
+
|
|
270
|
+
if (msgType === "String") msg = { body: msg };
|
|
271
|
+
|
|
272
|
+
var disallowedProperties = Object.keys(msg).filter(function (prop) { return !allowedProperties[prop]; });
|
|
273
|
+
if (disallowedProperties.length > 0) throw new Error("Disallowed props: `" + disallowedProperties.join(", ") + "`");
|
|
274
|
+
|
|
275
|
+
var configSource = (global.GoatBot && global.GoatBot.config) ? global.GoatBot.config : (ctx.config || {});
|
|
276
|
+
var enableTypingIndicator = typeof configSource.enableTypingIndicator !== 'undefined' ? configSource.enableTypingIndicator : (ctx.config && ctx.config.enableTypingIndicator);
|
|
277
|
+
var typingDuration = Number(configSource.typingDuration || (ctx.config && ctx.config.typingDuration) || 4000);
|
|
278
|
+
|
|
279
|
+
if (enableTypingIndicator) {
|
|
280
|
+
await api.sendTypingIndicator(true, threadID, function () { });
|
|
281
|
+
await utils.delay(typingDuration);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
var result = await sendViaMqtt(msg, threadID, replyToMessage);
|
|
286
|
+
if (enableTypingIndicator) {
|
|
287
|
+
api.sendTypingIndicator(false, threadID, function () { }).catch(function () { });
|
|
288
|
+
}
|
|
289
|
+
if (typeof callback === "function") callback(null, result);
|
|
290
|
+
return result;
|
|
291
|
+
} catch (mqttErr) {
|
|
292
|
+
log.warn("sendMessage", "MQTT send failed, falling back to HTTP: " + (mqttErr && mqttErr.message));
|
|
293
|
+
if (enableTypingIndicator) {
|
|
294
|
+
api.sendTypingIndicator(false, threadID, function () { }).catch(function () { });
|
|
295
|
+
}
|
|
296
|
+
return api.OldMessage(msg, threadID, callback, replyToMessage, isSingleUser);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
};
|
package/src/sendMessageMqtt.js
CHANGED
|
@@ -209,7 +209,6 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
209
209
|
task.payload = JSON.stringify(task.payload);
|
|
210
210
|
});
|
|
211
211
|
form.payload = JSON.stringify(form.payload);
|
|
212
|
-
console.log(global.jsonStringifyColor(form, null, 2));
|
|
213
212
|
|
|
214
213
|
return mqttClient.publish(
|
|
215
214
|
"/ls_req",
|