oc-browser-relay 1.0.21 → 1.0.22
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/bundled-relay/index.js +20 -20
- package/bundled-relay/public/configs/pages/alimama-account-report.json +59 -1
- package/bundled-relay/public/configs/task-templates/alimama-campaign-report-download.json +166 -0
- package/bundled-relay/public/configs/task-templates/index.json +1 -0
- package/bundled-relay/public/icons/128-off.png +0 -0
- package/bundled-relay/public/icons/128-on.png +0 -0
- package/bundled-relay/public/icons/48-off.png +0 -0
- package/bundled-relay/public/icons/48-on.png +0 -0
- package/bundled-relay/public/icons/96-off.png +0 -0
- package/bundled-relay/public/icons/96-on.png +0 -0
- package/index.js +27 -27
- package/package.json +1 -1
- package/skills/oc-browser-relay-alimama/SKILL.md +18 -1
- package/bundled-relay/package.json +0 -7
- package/bundled-relay/public/icons/icon-inactive-128.png +0 -0
- package/bundled-relay/public/icons/icon-inactive-48.png +0 -0
- package/bundled-relay/public/icons/icon-inactive-96.png +0 -0
package/bundled-relay/index.js
CHANGED
|
@@ -23166,7 +23166,7 @@ var TemplateExecutor = class _TemplateExecutor {
|
|
|
23166
23166
|
}
|
|
23167
23167
|
continue;
|
|
23168
23168
|
}
|
|
23169
|
-
console.
|
|
23169
|
+
console.log(`[TemplateExecutor] \u4E0D\u652F\u6301\u7684\u9A8C\u8BC1\u89C4\u5219: ${rule}`);
|
|
23170
23170
|
}
|
|
23171
23171
|
}
|
|
23172
23172
|
applyDefaults(template, data) {
|
|
@@ -25129,7 +25129,7 @@ async function startNextBatchItem(batchJobId) {
|
|
|
25129
25129
|
message: error?.message || "Runtime not available"
|
|
25130
25130
|
};
|
|
25131
25131
|
updateTaskStatusView(task);
|
|
25132
|
-
console.
|
|
25132
|
+
console.log(`Batch task ${task.taskId} failed:`, error);
|
|
25133
25133
|
await handleBatchTaskTerminal(task.taskId, "failed", task.lastError);
|
|
25134
25134
|
}
|
|
25135
25135
|
}
|
|
@@ -25178,7 +25178,7 @@ function handleHello(msg) {
|
|
|
25178
25178
|
const payloadClientId = typeof msg.payload?.clientId === "string" ? msg.payload.clientId : void 0;
|
|
25179
25179
|
const clientId = sourceClientId || payloadClientId;
|
|
25180
25180
|
if (!clientId) {
|
|
25181
|
-
console.
|
|
25181
|
+
console.log("[Relay] session.hello \u7F3A\u5C11 clientId");
|
|
25182
25182
|
return;
|
|
25183
25183
|
}
|
|
25184
25184
|
touchRuntimeConnection(clientId);
|
|
@@ -25202,7 +25202,7 @@ async function registerAndDispatchTask(task) {
|
|
|
25202
25202
|
message: error?.message || "Runtime not available"
|
|
25203
25203
|
};
|
|
25204
25204
|
updateTaskStatusView(task);
|
|
25205
|
-
console.
|
|
25205
|
+
console.log(`Task ${task.taskId} failed:`, error);
|
|
25206
25206
|
}
|
|
25207
25207
|
return task;
|
|
25208
25208
|
}
|
|
@@ -25251,7 +25251,7 @@ async function handleRuntimeMessage(msg) {
|
|
|
25251
25251
|
case "session.ping":
|
|
25252
25252
|
break;
|
|
25253
25253
|
default:
|
|
25254
|
-
console.
|
|
25254
|
+
console.log(`[Relay] \u672A\u5904\u7406\u7684 runtime \u6D88\u606F\u7C7B\u578B: ${msg.type}`);
|
|
25255
25255
|
break;
|
|
25256
25256
|
}
|
|
25257
25257
|
}
|
|
@@ -25368,13 +25368,13 @@ async function handleNodeResult(msg) {
|
|
|
25368
25368
|
const decision = guardNodeResult(task, orchestrator, rawResult);
|
|
25369
25369
|
if (!decision.accept) {
|
|
25370
25370
|
if (decision.reason === "task_missing") {
|
|
25371
|
-
console.
|
|
25371
|
+
console.log(`[Relay] \u5FFD\u7565 node.result\uFF1A\u4EFB\u52A1\u4E0D\u5B58\u5728 (${rawResult.taskId})`);
|
|
25372
25372
|
} else if (decision.reason === "orchestrator_missing") {
|
|
25373
|
-
console.
|
|
25373
|
+
console.log(`[Relay] \u5FFD\u7565 node.result\uFF1A\u7F16\u6392\u5668\u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u662F\u8FDF\u5230/\u91CD\u590D\u7ED3\u679C (${rawResult.taskId}/${rawResult.nodeId})`);
|
|
25374
25374
|
} else if (decision.reason === "node_missing") {
|
|
25375
|
-
console.
|
|
25375
|
+
console.log(`[Relay] \u5FFD\u7565 node.result\uFF1A\u5F53\u524D\u65E0\u53EF\u6267\u884C\u8282\u70B9\uFF0C\u53EF\u80FD\u662F\u8FDF\u5230\u7ED3\u679C (${rawResult.taskId}/${rawResult.nodeId})`);
|
|
25376
25376
|
} else {
|
|
25377
|
-
console.
|
|
25377
|
+
console.log(
|
|
25378
25378
|
`[Relay] \u5FFD\u7565 node.result\uFF1Anode \u4E0D\u5339\u914D\uFF0Cexpected=${decision.expectedNodeId}, got=${rawResult.nodeId}, task=${rawResult.taskId}`
|
|
25379
25379
|
);
|
|
25380
25380
|
}
|
|
@@ -25459,7 +25459,7 @@ function sendToClient(clientId, msg) {
|
|
|
25459
25459
|
const connection = runtimeConnections.get(clientId);
|
|
25460
25460
|
if (!connection || connection.res.writableEnded) {
|
|
25461
25461
|
runtimeConnections.delete(clientId);
|
|
25462
|
-
console.
|
|
25462
|
+
console.log(`[Relay] \u5BA2\u6237\u7AEF\u4E0D\u53EF\u7528: ${clientId}`);
|
|
25463
25463
|
return false;
|
|
25464
25464
|
}
|
|
25465
25465
|
try {
|
|
@@ -25471,7 +25471,7 @@ function sendToClient(clientId, msg) {
|
|
|
25471
25471
|
return true;
|
|
25472
25472
|
} catch (error) {
|
|
25473
25473
|
runtimeConnections.delete(clientId);
|
|
25474
|
-
console.
|
|
25474
|
+
console.log(`[Relay] \u5BA2\u6237\u7AEF\u53D1\u9001\u5931\u8D25: ${clientId}`, error);
|
|
25475
25475
|
return false;
|
|
25476
25476
|
}
|
|
25477
25477
|
}
|
|
@@ -25995,7 +25995,7 @@ app.post("/api/tasks", async (req, res) => {
|
|
|
25995
25995
|
console.log(`[Relay] \u4EFB\u52A1\u5DF2\u521B\u5EFA: ${taskId}\uFF0C\u5305\u542B ${task.assets.length} \u4E2A\u6587\u4EF6`);
|
|
25996
25996
|
res.json({ success: true, taskId, channel: "http" });
|
|
25997
25997
|
} catch (err) {
|
|
25998
|
-
console.
|
|
25998
|
+
console.log("[Relay] \u4EFB\u52A1\u63D0\u4EA4\u5931\u8D25:", err);
|
|
25999
25999
|
res.status(err?.status || 500).json({
|
|
26000
26000
|
success: false,
|
|
26001
26001
|
error: err?.message || "Task submission failed",
|
|
@@ -26109,12 +26109,12 @@ function startOrchestrator(taskId) {
|
|
|
26109
26109
|
console.log(`[Relay] \u542F\u52A8\u7F16\u6392\u5668: ${taskId}`);
|
|
26110
26110
|
const task = tasks.get(taskId);
|
|
26111
26111
|
if (!task) {
|
|
26112
|
-
console.
|
|
26112
|
+
console.log(`[Relay] \u4EFB\u52A1\u4E0D\u5B58\u5728: ${taskId}`);
|
|
26113
26113
|
return;
|
|
26114
26114
|
}
|
|
26115
26115
|
const orchestrator = createTaskOrchestrator(task);
|
|
26116
26116
|
if (!orchestrator) {
|
|
26117
|
-
console.
|
|
26117
|
+
console.log(`[Relay] \u4EFB\u52A1\u6CA1\u6709\u53EF\u6267\u884C\u8282\u70B9/\u6B65\u9AA4: ${taskId}`);
|
|
26118
26118
|
return;
|
|
26119
26119
|
}
|
|
26120
26120
|
const executionLength = getTaskExecutionLength(task);
|
|
@@ -26181,16 +26181,16 @@ async function sendNextStep(taskId) {
|
|
|
26181
26181
|
const task = tasks.get(taskId);
|
|
26182
26182
|
const orch = orchestrators.get(taskId);
|
|
26183
26183
|
if (!task) {
|
|
26184
|
-
console.
|
|
26184
|
+
console.log(`[Relay] \u4EFB\u52A1\u4E0D\u5B58\u5728: ${taskId}`);
|
|
26185
26185
|
return;
|
|
26186
26186
|
}
|
|
26187
26187
|
if (!orch) {
|
|
26188
|
-
console.
|
|
26188
|
+
console.log(`[Relay] \u7F16\u6392\u5668\u4E0D\u5B58\u5728: ${taskId}`);
|
|
26189
26189
|
return;
|
|
26190
26190
|
}
|
|
26191
26191
|
const executionLength = getTaskExecutionLength(task);
|
|
26192
26192
|
if (executionLength === 0) {
|
|
26193
|
-
console.
|
|
26193
|
+
console.log(`[Relay] \u4EFB\u52A1\u6CA1\u6709\u53EF\u6267\u884C\u8282\u70B9`);
|
|
26194
26194
|
return;
|
|
26195
26195
|
}
|
|
26196
26196
|
console.log(`[Relay] \u5F53\u524D\u8282\u70B9\u7D22\u5F15: ${orch.currentStepIndex}, \u603B\u8282\u70B9\u6570: ${executionLength}`);
|
|
@@ -26246,7 +26246,7 @@ async function sendNextStep(taskId) {
|
|
|
26246
26246
|
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26247
26247
|
updateTaskStatusView(task);
|
|
26248
26248
|
orchestrators.delete(taskId);
|
|
26249
|
-
console.
|
|
26249
|
+
console.log(`[Relay] \u4EFB\u52A1\u7F3A\u5C11\u76EE\u6807 Runtime\uFF0C\u505C\u6B62\u4EFB\u52A1: ${taskId}`);
|
|
26250
26250
|
notifyRuntimeTaskFinalized(task, "failed");
|
|
26251
26251
|
void handleBatchTaskTerminal(taskId, "failed", task.lastError);
|
|
26252
26252
|
return;
|
|
@@ -26262,7 +26262,7 @@ async function sendNextStep(taskId) {
|
|
|
26262
26262
|
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26263
26263
|
updateTaskStatusView(task);
|
|
26264
26264
|
orchestrators.delete(taskId);
|
|
26265
|
-
console.
|
|
26265
|
+
console.log(`[Relay] \u76EE\u6807 Runtime \u4E0D\u53EF\u7528\uFF0C\u505C\u6B62\u4EFB\u52A1: ${taskId}`);
|
|
26266
26266
|
notifyRuntimeTaskFinalized(task, "failed");
|
|
26267
26267
|
void handleBatchTaskTerminal(taskId, "failed", task.lastError);
|
|
26268
26268
|
return;
|
|
@@ -26285,7 +26285,7 @@ function continueOrchestrator(taskId, completedNodeId, nextStepIndex) {
|
|
|
26285
26285
|
console.log(`[Relay] continueOrchestrator \u88AB\u8C03\u7528: ${taskId}, nodeId: ${completedNodeId}`);
|
|
26286
26286
|
const orch = orchestrators.get(taskId);
|
|
26287
26287
|
if (!orch) {
|
|
26288
|
-
console.
|
|
26288
|
+
console.log(`[Relay] \u7F16\u6392\u5668\u4E0D\u5B58\u5728: ${taskId}`);
|
|
26289
26289
|
return;
|
|
26290
26290
|
}
|
|
26291
26291
|
advanceTaskOrchestrator(orch, nextStepIndex);
|
|
@@ -163,6 +163,64 @@
|
|
|
163
163
|
"responsePath": "data.taskId",
|
|
164
164
|
"metaPath": "data"
|
|
165
165
|
},
|
|
166
|
+
"create_campaign_download_task": {
|
|
167
|
+
"method": "POST",
|
|
168
|
+
"url": "https://one.alimama.com/report/createDownLoadTask.json",
|
|
169
|
+
"query": {
|
|
170
|
+
"csrfId": "{{payload.csrfId}}",
|
|
171
|
+
"bizCode": "universalBP"
|
|
172
|
+
},
|
|
173
|
+
"headers": {
|
|
174
|
+
"Content-Type": "application/json"
|
|
175
|
+
},
|
|
176
|
+
"body": {
|
|
177
|
+
"excelName": "{{payload.excelName}}",
|
|
178
|
+
"pageSize": 60,
|
|
179
|
+
"offset": 0,
|
|
180
|
+
"havingList": [],
|
|
181
|
+
"endTime": "{{payload.endTime}}",
|
|
182
|
+
"unifyType": "zhai",
|
|
183
|
+
"effectEqual": 15,
|
|
184
|
+
"startTime": "{{payload.startTime}}",
|
|
185
|
+
"splitType": "{{payload.splitType}}",
|
|
186
|
+
"from": "pcBaseReport",
|
|
187
|
+
"queryFieldIn": [
|
|
188
|
+
"adPv",
|
|
189
|
+
"click",
|
|
190
|
+
"charge",
|
|
191
|
+
"ctr",
|
|
192
|
+
"ecpc",
|
|
193
|
+
"alipayInshopAmt",
|
|
194
|
+
"alipayInshopNum",
|
|
195
|
+
"cvr",
|
|
196
|
+
"cartInshopNum",
|
|
197
|
+
"itemColInshopNum",
|
|
198
|
+
"shopColDirNum",
|
|
199
|
+
"colNum",
|
|
200
|
+
"itemColInshopCost"
|
|
201
|
+
],
|
|
202
|
+
"vsType": "{{payload.vsType}}",
|
|
203
|
+
"vsTime": "{{payload.vsTime}}",
|
|
204
|
+
"searchValue": "{{payload.searchValue}}",
|
|
205
|
+
"searchKey": "strategyCampaignIdOrName",
|
|
206
|
+
"queryDomains": [
|
|
207
|
+
"campaign",
|
|
208
|
+
"date",
|
|
209
|
+
"original_scene"
|
|
210
|
+
],
|
|
211
|
+
"fieldType": "all",
|
|
212
|
+
"rptType": "campaign",
|
|
213
|
+
"parentAdcName": "report_frame_campaign",
|
|
214
|
+
"byPage": false,
|
|
215
|
+
"fromRealTime": false,
|
|
216
|
+
"source": "async_dowdload",
|
|
217
|
+
"csrfId": "{{payload.csrfId}}",
|
|
218
|
+
"bizCode": "universalBP",
|
|
219
|
+
"loginPointId": "{{payload.loginPointId}}"
|
|
220
|
+
},
|
|
221
|
+
"responsePath": "data.taskId",
|
|
222
|
+
"metaPath": "data"
|
|
223
|
+
},
|
|
166
224
|
"get_download_url": {
|
|
167
225
|
"method": "GET",
|
|
168
226
|
"url": "https://bpcommon.alimama.com/commonapi/report/async/getDownloadUrl.json",
|
|
@@ -201,4 +259,4 @@
|
|
|
201
259
|
}
|
|
202
260
|
}
|
|
203
261
|
}
|
|
204
|
-
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"siteId": "alimama_report",
|
|
3
|
+
"taskType": "download_campaign_report",
|
|
4
|
+
"platform": "taobao",
|
|
5
|
+
"name": "下载阿里妈妈计划报表",
|
|
6
|
+
"description": "打开阿里妈妈账户报表页,捕获鉴权信息,创建计划报表异步下载任务,轮询下载链接并将文件回传 relay 存储。",
|
|
7
|
+
"requiredFields": [
|
|
8
|
+
{
|
|
9
|
+
"name": "startTime",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "报表开始日期,格式 YYYY-MM-DD"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "endTime",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "报表结束日期,格式 YYYY-MM-DD"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"optionalFields": [
|
|
20
|
+
{
|
|
21
|
+
"name": "splitType",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "报表时间粒度,支持 sum/hour/day/week/month/calendarmonth",
|
|
24
|
+
"default": "sum"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "returnHostPatterns",
|
|
28
|
+
"type": "array",
|
|
29
|
+
"description": "任务结束后若存在匹配这些 host 或 URL 片段的标签页,则切回该标签页"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"nodes": [
|
|
33
|
+
{
|
|
34
|
+
"nodeId": "s1",
|
|
35
|
+
"name": "open_alimama_report",
|
|
36
|
+
"goal": "打开阿里妈妈账户报表页并预抓鉴权接口",
|
|
37
|
+
"tool": "open_tab",
|
|
38
|
+
"args": {
|
|
39
|
+
"url": "https://one.alimama.com/index.html#!/report/account?rptType=account",
|
|
40
|
+
"captureNetwork": {
|
|
41
|
+
"capturePlanId": "auth_info"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"nodeId": "s2",
|
|
47
|
+
"name": "validate_alimama_report",
|
|
48
|
+
"goal": "校验当前页面已进入账户报表页",
|
|
49
|
+
"tool": "validate_page",
|
|
50
|
+
"args": {}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"nodeId": "s3",
|
|
54
|
+
"name": "collect_auth_info",
|
|
55
|
+
"goal": "从 checkAccess 响应中提取 csrfId 和 loginPointId",
|
|
56
|
+
"tool": "collect_structured_data",
|
|
57
|
+
"args": {
|
|
58
|
+
"extractor": "download_auth_info",
|
|
59
|
+
"sourceNodeId": "s1"
|
|
60
|
+
},
|
|
61
|
+
"storeAs": "authInfo"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"nodeId": "s4",
|
|
65
|
+
"name": "show_download_activity",
|
|
66
|
+
"goal": "在页面顶部展示独立提示,标记当前正在准备计划报表下载",
|
|
67
|
+
"tool": "present_page_activity",
|
|
68
|
+
"args": {
|
|
69
|
+
"mode": "start",
|
|
70
|
+
"message": "正在提交报表下载任务",
|
|
71
|
+
"style": "info",
|
|
72
|
+
"profile": "none"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"nodeId": "s5",
|
|
77
|
+
"name": "create_campaign_download_task",
|
|
78
|
+
"goal": "创建计划报表异步下载任务",
|
|
79
|
+
"tool": "invoke_page_request",
|
|
80
|
+
"args": {
|
|
81
|
+
"sourceId": "create_campaign_download_task",
|
|
82
|
+
"payload": {
|
|
83
|
+
"startTime": "{{data.startTime}}",
|
|
84
|
+
"endTime": "{{data.endTime}}",
|
|
85
|
+
"vsTime": "{{data.endTime}}",
|
|
86
|
+
"vsType": "week",
|
|
87
|
+
"searchValue": "",
|
|
88
|
+
"splitType": "{{data.splitType}}",
|
|
89
|
+
"excelName": "计划报表_{{data.startTime}}_{{data.endTime}}"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"fromContext": {
|
|
93
|
+
"payload.csrfId": "outputs.authInfo.data.csrfId",
|
|
94
|
+
"payload.loginPointId": "outputs.authInfo.data.loginPointId"
|
|
95
|
+
},
|
|
96
|
+
"storeAs": "downloadTask"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"nodeId": "s6",
|
|
100
|
+
"name": "update_download_activity",
|
|
101
|
+
"goal": "更新页面提示为等待报表生成完成",
|
|
102
|
+
"tool": "present_page_activity",
|
|
103
|
+
"args": {
|
|
104
|
+
"mode": "start",
|
|
105
|
+
"message": "正在等待报表生成完成",
|
|
106
|
+
"style": "info",
|
|
107
|
+
"profile": "none"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"nodeId": "s7",
|
|
112
|
+
"name": "poll_download_url",
|
|
113
|
+
"goal": "等待下载任务生成链接并轮询拿到下载地址",
|
|
114
|
+
"tool": "invoke_page_request",
|
|
115
|
+
"args": {
|
|
116
|
+
"sourceId": "get_download_url",
|
|
117
|
+
"payload": {},
|
|
118
|
+
"polling": {
|
|
119
|
+
"initialDelayMs": 30000,
|
|
120
|
+
"intervalMs": 5000,
|
|
121
|
+
"timeoutMs": 120000,
|
|
122
|
+
"maxAttempts": 25
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"fromContext": {
|
|
126
|
+
"payload.taskId": "outputs.downloadTask.response.data",
|
|
127
|
+
"payload.csrfId": "outputs.authInfo.data.csrfId",
|
|
128
|
+
"payload.loginPointId": "outputs.authInfo.data.loginPointId"
|
|
129
|
+
},
|
|
130
|
+
"storeAs": "downloadLink"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"nodeId": "s8",
|
|
134
|
+
"name": "download_report_file",
|
|
135
|
+
"goal": "拉取下载链接并把文件内容回传给 relay 存储",
|
|
136
|
+
"tool": "download_page_file",
|
|
137
|
+
"args": {
|
|
138
|
+
"filename": "计划报表_{{data.startTime}}_{{data.endTime}}.zip"
|
|
139
|
+
},
|
|
140
|
+
"fromContext": {
|
|
141
|
+
"url": "outputs.downloadLink.response.data"
|
|
142
|
+
},
|
|
143
|
+
"storeAs": "downloadResult"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"nodeId": "s9",
|
|
147
|
+
"name": "hide_download_activity",
|
|
148
|
+
"goal": "在下载链路结束后关闭页面提示",
|
|
149
|
+
"tool": "present_page_activity",
|
|
150
|
+
"args": {
|
|
151
|
+
"mode": "stop"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"nodeId": "s10",
|
|
156
|
+
"name": "switch_to_existing_host_tab",
|
|
157
|
+
"goal": "任务结束后若存在匹配的既有标签页则切回去",
|
|
158
|
+
"when": "exists(payload.returnHostPatterns)",
|
|
159
|
+
"tool": "switch_tab",
|
|
160
|
+
"args": {
|
|
161
|
+
"urlIncludesAny": "{{data.returnHostPatterns}}",
|
|
162
|
+
"optional": true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|