poi-plugin-mcp 0.2.21 → 0.2.23
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/index.js +1 -0
- package/lib/bridge-controller.js +1 -0
- package/lib/poi-http-bridge.js +7 -0
- package/lib/poi-input.js +20 -12
- package/lib/poi-interaction-recorder.js +2 -2
- package/lib/poi-telemetry.js +44 -0
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const { createSettingsClass } = require('./lib/settings-view')
|
|
|
5
5
|
const telemetry = createPoiTelemetry()
|
|
6
6
|
const controller = createBridgeController({
|
|
7
7
|
getQuestList: telemetry.getQuestList,
|
|
8
|
+
getMissionBoard: telemetry.getMissionBoard,
|
|
8
9
|
getQuestAction: telemetry.getQuestAction,
|
|
9
10
|
getEquipmentAction: telemetry.getEquipmentAction,
|
|
10
11
|
getEquipmentSelection: telemetry.getEquipmentSelection,
|
package/lib/bridge-controller.js
CHANGED
|
@@ -32,6 +32,7 @@ function createBridgeController(options = {}) {
|
|
|
32
32
|
captureScreenshot: options.captureScreenshot,
|
|
33
33
|
performInput: options.performInput,
|
|
34
34
|
getQuestList: options.getQuestList,
|
|
35
|
+
getMissionBoard: options.getMissionBoard,
|
|
35
36
|
getQuestAction: options.getQuestAction,
|
|
36
37
|
getEquipmentAction: options.getEquipmentAction,
|
|
37
38
|
getEquipmentSelection: options.getEquipmentSelection,
|
package/lib/poi-http-bridge.js
CHANGED
|
@@ -71,6 +71,8 @@ function createPoiDataBridge(options = {}) {
|
|
|
71
71
|
const masterFile = options.masterFile || DEFAULT_MASTER_FILE
|
|
72
72
|
const logger = options.logger || console
|
|
73
73
|
const getQuestList = options.getQuestList || (() => ({ available: false, generation: 0 }))
|
|
74
|
+
const getMissionBoard = options.getMissionBoard ||
|
|
75
|
+
(() => ({ available: false, generation: 0 }))
|
|
74
76
|
const getQuestAction = options.getQuestAction || (() => ({ available: false, generation: 0 }))
|
|
75
77
|
const getEquipmentAction = options.getEquipmentAction ||
|
|
76
78
|
(() => ({ available: false, generation: 0 }))
|
|
@@ -591,6 +593,11 @@ function createPoiDataBridge(options = {}) {
|
|
|
591
593
|
return
|
|
592
594
|
}
|
|
593
595
|
|
|
596
|
+
if (endpoint === '/mission-board') {
|
|
597
|
+
sendJson(res, 200, getMissionBoard())
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
|
|
594
601
|
if (endpoint === '/quest-action') {
|
|
595
602
|
sendJson(res, 200, getQuestAction())
|
|
596
603
|
return
|
package/lib/poi-input.js
CHANGED
|
@@ -32,12 +32,14 @@ function createPoiInputProvider(options = {}) {
|
|
|
32
32
|
return async function performPoiInput(operation) {
|
|
33
33
|
validateOperationObject(operation)
|
|
34
34
|
const layout = readLayout(getStore, resolveWebContents)
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
35
|
+
// Live 2026-08-24: synthetic clicks reach the game and take effect in a
|
|
36
|
+
// visible, non-focused window; they only stall when the window is
|
|
37
|
+
// minimized/occluded and Chromium throttles the renderer (PIXI ticker
|
|
38
|
+
// frozen). So: un-throttle the webContents, restore a minimized window,
|
|
39
|
+
// and keep the document focused at the webContents level — never steal
|
|
40
|
+
// the user's OS foreground (the Windows foreground lock silently ignored
|
|
41
|
+
// the old win.focus() anyway).
|
|
42
|
+
prepareGameWindowForInput(layout.webContents)
|
|
41
43
|
|
|
42
44
|
switch (operation.operation) {
|
|
43
45
|
case 'move':
|
|
@@ -84,17 +86,23 @@ function validateOperationObject(operation) {
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
function
|
|
89
|
+
function prepareGameWindowForInput(webContents) {
|
|
90
|
+
try {
|
|
91
|
+
if (typeof webContents.setBackgroundThrottling === 'function') {
|
|
92
|
+
webContents.setBackgroundThrottling(false)
|
|
93
|
+
}
|
|
94
|
+
} catch (_throttleError) {
|
|
95
|
+
// Throttling control is best-effort; input below is still delivered.
|
|
96
|
+
}
|
|
88
97
|
try {
|
|
89
98
|
const { BrowserWindow } = require('electron')
|
|
90
99
|
const win = BrowserWindow.fromWebContents(
|
|
91
100
|
webContents.hostWebContents || webContents,
|
|
92
101
|
)
|
|
93
|
-
if (win &&
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
win.focus()
|
|
102
|
+
if (win && win.isMinimized()) {
|
|
103
|
+
// Restoring resumes the throttled renderer; it does not need the
|
|
104
|
+
// OS foreground for synthetic input to work (verified 2026-08-24).
|
|
105
|
+
win.restore()
|
|
98
106
|
}
|
|
99
107
|
} catch (_windowError) {
|
|
100
108
|
// Fall through to webContents-level focus below.
|
|
@@ -21,8 +21,8 @@ const DEFAULT_MAX_TOTAL_RECORDING_BYTES = 64 * 1024 * 1024 * 1024
|
|
|
21
21
|
const DEFAULT_FINAL_MANIFEST_RESERVE_BYTES = 64 * 1024
|
|
22
22
|
const DEFAULT_CHECKPOINT_DELAYS_MS = Object.freeze([0, 250, 1000])
|
|
23
23
|
const DEFAULT_OUTPUT_ROOT = process.platform === 'win32'
|
|
24
|
-
? '
|
|
25
|
-
: path.join(os.homedir(), '.poi-mcp', 'recordings')
|
|
24
|
+
? (process.env.POI_MCP_RECORDING_DIR || 'E:\\kancolle\\recordings')
|
|
25
|
+
: (process.env.POI_MCP_RECORDING_DIR || path.join(os.homedir(), '.poi-mcp', 'recordings'))
|
|
26
26
|
const SENSITIVE_KEY = /(auth|authorization|cookie|credential|key|login(?:data)?|password|secret|session|sid|ticket|token)/iu
|
|
27
27
|
const SENSITIVE_VALUE = /\b(?:basic|bearer|api[_-]?token|access[_-]?token|refresh[_-]?token|session[_-]?id)\b/iu
|
|
28
28
|
const GAME_BUSINESS_SORT_KEYS = new Set([
|
package/lib/poi-telemetry.js
CHANGED
|
@@ -2,6 +2,7 @@ const { createPoiActionEvents } = require('./poi-action-events')
|
|
|
2
2
|
const { createPoiApiResponses } = require('./poi-api-responses')
|
|
3
3
|
|
|
4
4
|
const QUEST_LIST_PATH = '/kcsapi/api_get_member/questlist'
|
|
5
|
+
const MISSION_BOARD_PATH = '/kcsapi/api_get_member/mission'
|
|
5
6
|
const UNSET_SLOT_PATH = '/kcsapi/api_get_member/unsetslot'
|
|
6
7
|
const EQUIPMENT_SELECTION_PATH = '/kcsapi/api_req_kaisou/slot_select'
|
|
7
8
|
const QUEST_ACTION_PATHS = new Set([
|
|
@@ -50,6 +51,8 @@ function createPoiTelemetry(options = {}) {
|
|
|
50
51
|
const apiResponses = createPoiApiResponses({ now })
|
|
51
52
|
let questGeneration = 0
|
|
52
53
|
let questList = null
|
|
54
|
+
let missionBoardGeneration = 0
|
|
55
|
+
let missionBoard = null
|
|
53
56
|
let questActionGeneration = 0
|
|
54
57
|
let questAction = null
|
|
55
58
|
let equipmentActionGeneration = 0
|
|
@@ -73,6 +76,10 @@ function createPoiTelemetry(options = {}) {
|
|
|
73
76
|
captureQuestList(detail, actionEvent)
|
|
74
77
|
return
|
|
75
78
|
}
|
|
79
|
+
if (detail.path === MISSION_BOARD_PATH) {
|
|
80
|
+
captureMissionBoard(detail, actionEvent)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
76
83
|
if (detail.path === UNSET_SLOT_PATH) {
|
|
77
84
|
captureUnsetSlot(detail, actionEvent)
|
|
78
85
|
return
|
|
@@ -140,6 +147,40 @@ function createPoiTelemetry(options = {}) {
|
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
function captureMissionBoard(detail, actionEvent) {
|
|
151
|
+
const body = detail.body
|
|
152
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return
|
|
153
|
+
const source = body.api_data &&
|
|
154
|
+
typeof body.api_data === 'object' &&
|
|
155
|
+
!Array.isArray(body.api_data)
|
|
156
|
+
? body.api_data
|
|
157
|
+
: body
|
|
158
|
+
if (!Array.isArray(source.api_list_items)) return
|
|
159
|
+
|
|
160
|
+
const items = source.api_list_items.flatMap((item) => {
|
|
161
|
+
const missionId = positiveIntegerOrNull(item && item.api_mission_id)
|
|
162
|
+
const state = toInteger(item && item.api_state)
|
|
163
|
+
if (missionId == null || state == null || state < 0) return []
|
|
164
|
+
return [{ missionId, state }]
|
|
165
|
+
})
|
|
166
|
+
if (items.length === 0) return
|
|
167
|
+
|
|
168
|
+
missionBoardGeneration += 1
|
|
169
|
+
missionBoard = {
|
|
170
|
+
available: true,
|
|
171
|
+
generation: missionBoardGeneration,
|
|
172
|
+
capturedAt: actionEvent
|
|
173
|
+
? actionEvent.capturedAt
|
|
174
|
+
: now().toISOString(),
|
|
175
|
+
items,
|
|
176
|
+
limitTime: positiveIntegerOrNull(
|
|
177
|
+
Array.isArray(source.api_limit_time)
|
|
178
|
+
? source.api_limit_time[0]
|
|
179
|
+
: source.api_limit_time,
|
|
180
|
+
),
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
143
184
|
function captureQuestAction(detail, actionEvent) {
|
|
144
185
|
const apiResult = actionEvent
|
|
145
186
|
? actionEvent.apiResult
|
|
@@ -303,6 +344,9 @@ function createPoiTelemetry(options = {}) {
|
|
|
303
344
|
getQuestList() {
|
|
304
345
|
return questList || { available: false, generation: 0 }
|
|
305
346
|
},
|
|
347
|
+
getMissionBoard() {
|
|
348
|
+
return missionBoard || { available: false, generation: 0 }
|
|
349
|
+
},
|
|
306
350
|
getQuestAction() {
|
|
307
351
|
return questAction || { available: false, generation: 0 }
|
|
308
352
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
{
|
|
2
2
|
"name": "poi-plugin-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.23",
|
|
4
4
|
"description": "Poi data, WebView capture, and opt-in authenticated input bridge for local KanColle tools.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test": "node --test test/*.test.js"
|
|
23
23
|
},
|
|
24
24
|
"poiPlugin": {
|
|
25
|
-
"title": "MCP
|
|
25
|
+
"title": "MCP 数据桥",
|
|
26
26
|
"id": "mcp_data_bridge",
|
|
27
27
|
"description": "Expose Poi state, in-memory game WebView captures, and opt-in authenticated input on a local bridge.",
|
|
28
28
|
"icon": "fa/database",
|