poi-plugin-mcp 0.2.16 → 0.2.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/README.md +290 -201
- package/index.js +3 -0
- package/lib/bridge-controller.js +75 -1
- package/lib/fleet-metrics.js +249 -0
- package/lib/poi-action-events.js +405 -292
- package/lib/poi-api-responses.js +165 -0
- package/lib/poi-auto-interaction-recorder.js +399 -0
- package/lib/poi-data-query.js +254 -0
- package/lib/poi-http-bridge.js +1673 -1320
- package/lib/poi-input.js +374 -302
- package/lib/poi-interaction-recorder.js +1693 -0
- package/lib/poi-telemetry.js +515 -439
- package/lib/poi-webview-runtime.js +1181 -0
- package/lib/settings-view.js +112 -0
- package/lib/settings.js +8 -0
- package/mcp-server.js +542 -456
- package/package.json +1 -1
package/lib/poi-input.js
CHANGED
|
@@ -1,302 +1,374 @@
|
|
|
1
|
-
const CANONICAL_WIDTH = 1200
|
|
2
|
-
const CANONICAL_HEIGHT = 720
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const MAX_TEXT_LENGTH = 256
|
|
6
|
-
|
|
7
|
-
const SUPPORTED_BUTTONS = new Set(['left', 'middle', 'right'])
|
|
8
|
-
const SUPPORTED_KEY_EVENTS = new Set(['keyDown', 'keyUp'])
|
|
9
|
-
const SUPPORTED_KEYS = new Set([
|
|
10
|
-
'Backspace',
|
|
11
|
-
'Delete',
|
|
12
|
-
'End',
|
|
13
|
-
'Enter',
|
|
14
|
-
'Escape',
|
|
15
|
-
'Home',
|
|
16
|
-
'PageDown',
|
|
17
|
-
'PageUp',
|
|
18
|
-
'Space',
|
|
19
|
-
'Tab',
|
|
20
|
-
'ArrowDown',
|
|
21
|
-
'ArrowLeft',
|
|
22
|
-
'ArrowRight',
|
|
23
|
-
'ArrowUp',
|
|
24
|
-
])
|
|
25
|
-
|
|
26
|
-
function createPoiInputProvider(options = {}) {
|
|
27
|
-
const getStore = options.getStore || defaultGetStore
|
|
28
|
-
const resolveWebContents =
|
|
29
|
-
options.resolveWebContents || defaultResolveWebContents
|
|
30
|
-
const delay = options.delay || defaultDelay
|
|
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
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
operation.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!Number.
|
|
169
|
-
throw new Error('
|
|
170
|
-
}
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
operation.
|
|
194
|
-
operation.
|
|
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
|
-
button: operation.button
|
|
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
|
-
|
|
1
|
+
const CANONICAL_WIDTH = 1200
|
|
2
|
+
const CANONICAL_HEIGHT = 720
|
|
3
|
+
const DRAG_MOVE_STEPS = 6
|
|
4
|
+
const DRAG_RELEASE_DELAY_MS = 120
|
|
5
|
+
const MAX_TEXT_LENGTH = 256
|
|
6
|
+
|
|
7
|
+
const SUPPORTED_BUTTONS = new Set(['left', 'middle', 'right'])
|
|
8
|
+
const SUPPORTED_KEY_EVENTS = new Set(['keyDown', 'keyUp'])
|
|
9
|
+
const SUPPORTED_KEYS = new Set([
|
|
10
|
+
'Backspace',
|
|
11
|
+
'Delete',
|
|
12
|
+
'End',
|
|
13
|
+
'Enter',
|
|
14
|
+
'Escape',
|
|
15
|
+
'Home',
|
|
16
|
+
'PageDown',
|
|
17
|
+
'PageUp',
|
|
18
|
+
'Space',
|
|
19
|
+
'Tab',
|
|
20
|
+
'ArrowDown',
|
|
21
|
+
'ArrowLeft',
|
|
22
|
+
'ArrowRight',
|
|
23
|
+
'ArrowUp',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
function createPoiInputProvider(options = {}) {
|
|
27
|
+
const getStore = options.getStore || defaultGetStore
|
|
28
|
+
const resolveWebContents =
|
|
29
|
+
options.resolveWebContents || defaultResolveWebContents
|
|
30
|
+
const delay = options.delay || defaultDelay
|
|
31
|
+
|
|
32
|
+
return async function performPoiInput(operation) {
|
|
33
|
+
validateOperationObject(operation)
|
|
34
|
+
const layout = readLayout(getStore, resolveWebContents)
|
|
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)
|
|
43
|
+
|
|
44
|
+
switch (operation.operation) {
|
|
45
|
+
case 'move':
|
|
46
|
+
validateMove(operation)
|
|
47
|
+
sendMove(layout, operation)
|
|
48
|
+
return 'move'
|
|
49
|
+
case 'click':
|
|
50
|
+
validateClick(operation)
|
|
51
|
+
sendClick(layout, operation)
|
|
52
|
+
return 'click'
|
|
53
|
+
case 'drag':
|
|
54
|
+
validateDrag(operation)
|
|
55
|
+
await sendDrag(layout, operation, delay)
|
|
56
|
+
return 'drag'
|
|
57
|
+
case 'key':
|
|
58
|
+
validateKey(operation)
|
|
59
|
+
await layout.webContents.sendInputEvent({
|
|
60
|
+
type: operation.event,
|
|
61
|
+
keyCode: operation.key,
|
|
62
|
+
})
|
|
63
|
+
return 'key'
|
|
64
|
+
case 'text':
|
|
65
|
+
validateText(operation)
|
|
66
|
+
for (const character of operation.text) {
|
|
67
|
+
await layout.webContents.sendInputEvent({
|
|
68
|
+
type: 'char',
|
|
69
|
+
keyCode: character,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
return 'text'
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`Unsupported input operation: ${String(operation.operation)}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateOperationObject(operation) {
|
|
80
|
+
if (
|
|
81
|
+
!operation ||
|
|
82
|
+
typeof operation !== 'object' ||
|
|
83
|
+
Array.isArray(operation)
|
|
84
|
+
) {
|
|
85
|
+
throw new Error('Input must be one operation object')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
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
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const { BrowserWindow } = require('electron')
|
|
99
|
+
const win = BrowserWindow.fromWebContents(
|
|
100
|
+
webContents.hostWebContents || webContents,
|
|
101
|
+
)
|
|
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()
|
|
106
|
+
}
|
|
107
|
+
} catch (_windowError) {
|
|
108
|
+
// Fall through to webContents-level focus below.
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
if (typeof webContents.focus === 'function') webContents.focus()
|
|
112
|
+
} catch (_focusError) {
|
|
113
|
+
// Input below is still delivered and may work without focus.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readLayout(getStore, resolveWebContents) {
|
|
118
|
+
const layout = getStore('layout.webview')
|
|
119
|
+
if (!layout || !layout.ref) {
|
|
120
|
+
throw new Error('Poi game WebView is not ready')
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
!Number.isFinite(layout.width) ||
|
|
124
|
+
layout.width <= 0 ||
|
|
125
|
+
!Number.isFinite(layout.height) ||
|
|
126
|
+
layout.height <= 0
|
|
127
|
+
) {
|
|
128
|
+
throw new Error('Poi game WebView dimensions must be positive finite numbers')
|
|
129
|
+
}
|
|
130
|
+
let webContents
|
|
131
|
+
if (typeof layout.ref.getWebContents === 'function') {
|
|
132
|
+
webContents = layout.ref.getWebContents()
|
|
133
|
+
} else if (typeof layout.ref.getWebContentsId === 'function') {
|
|
134
|
+
const webContentsId = layout.ref.getWebContentsId()
|
|
135
|
+
if (!Number.isInteger(webContentsId) || webContentsId <= 0) {
|
|
136
|
+
throw new Error('Poi game WebContents id is invalid')
|
|
137
|
+
}
|
|
138
|
+
webContents = resolveWebContents(webContentsId)
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error('Poi game WebView is not ready')
|
|
141
|
+
}
|
|
142
|
+
if (!webContents || typeof webContents.sendInputEvent !== 'function') {
|
|
143
|
+
throw new Error('Poi game WebContents is not ready')
|
|
144
|
+
}
|
|
145
|
+
return { ...layout, webContents }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function validateClick(operation) {
|
|
149
|
+
assertExactFields(operation, ['operation', 'x', 'y', 'button'])
|
|
150
|
+
if (!Number.isFinite(operation.x) || !Number.isFinite(operation.y)) {
|
|
151
|
+
throw new Error('Click coordinates must be finite numbers')
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
operation.x < 0 ||
|
|
155
|
+
operation.x >= CANONICAL_WIDTH ||
|
|
156
|
+
operation.y < 0 ||
|
|
157
|
+
operation.y >= CANONICAL_HEIGHT
|
|
158
|
+
) {
|
|
159
|
+
throw new Error('Click coordinates must be within canonical bounds')
|
|
160
|
+
}
|
|
161
|
+
if (!SUPPORTED_BUTTONS.has(operation.button)) {
|
|
162
|
+
throw new Error(`Unsupported mouse button: ${String(operation.button)}`)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validateMove(operation) {
|
|
167
|
+
assertExactFields(operation, ['operation', 'x', 'y'])
|
|
168
|
+
if (!Number.isFinite(operation.x) || !Number.isFinite(operation.y)) {
|
|
169
|
+
throw new Error('Move coordinates must be finite numbers')
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
operation.x < 0 ||
|
|
173
|
+
operation.x >= CANONICAL_WIDTH ||
|
|
174
|
+
operation.y < 0 ||
|
|
175
|
+
operation.y >= CANONICAL_HEIGHT
|
|
176
|
+
) {
|
|
177
|
+
throw new Error('Move coordinates must be within canonical bounds')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function validateDrag(operation) {
|
|
182
|
+
assertExactFields(operation, [
|
|
183
|
+
'operation',
|
|
184
|
+
'fromX',
|
|
185
|
+
'fromY',
|
|
186
|
+
'toX',
|
|
187
|
+
'toY',
|
|
188
|
+
'durationMs',
|
|
189
|
+
'button',
|
|
190
|
+
])
|
|
191
|
+
const coordinates = [
|
|
192
|
+
operation.fromX,
|
|
193
|
+
operation.fromY,
|
|
194
|
+
operation.toX,
|
|
195
|
+
operation.toY,
|
|
196
|
+
]
|
|
197
|
+
if (coordinates.some((coordinate) => !Number.isFinite(coordinate))) {
|
|
198
|
+
throw new Error('Drag coordinates must be finite numbers')
|
|
199
|
+
}
|
|
200
|
+
if (
|
|
201
|
+
operation.fromX < 0 ||
|
|
202
|
+
operation.fromX >= CANONICAL_WIDTH ||
|
|
203
|
+
operation.toX < 0 ||
|
|
204
|
+
operation.toX >= CANONICAL_WIDTH ||
|
|
205
|
+
operation.fromY < 0 ||
|
|
206
|
+
operation.fromY >= CANONICAL_HEIGHT ||
|
|
207
|
+
operation.toY < 0 ||
|
|
208
|
+
operation.toY >= CANONICAL_HEIGHT
|
|
209
|
+
) {
|
|
210
|
+
throw new Error('Drag coordinates must be within canonical bounds')
|
|
211
|
+
}
|
|
212
|
+
if (!Number.isInteger(operation.durationMs)) {
|
|
213
|
+
throw new Error('Drag durationMs must be an integer')
|
|
214
|
+
}
|
|
215
|
+
if (operation.durationMs < 50 || operation.durationMs > 2000) {
|
|
216
|
+
throw new Error('Drag durationMs must be from 50 to 2000')
|
|
217
|
+
}
|
|
218
|
+
if (!SUPPORTED_BUTTONS.has(operation.button)) {
|
|
219
|
+
throw new Error(`Unsupported mouse button: ${String(operation.button)}`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function validateKey(operation) {
|
|
224
|
+
assertExactFields(operation, ['operation', 'event', 'key'])
|
|
225
|
+
if (!SUPPORTED_KEY_EVENTS.has(operation.event)) {
|
|
226
|
+
throw new Error(`Unsupported key event: ${String(operation.event)}`)
|
|
227
|
+
}
|
|
228
|
+
if (!SUPPORTED_KEYS.has(operation.key)) {
|
|
229
|
+
throw new Error(`Unsupported key: ${String(operation.key)}`)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function validateText(operation) {
|
|
234
|
+
assertExactFields(operation, ['operation', 'text'])
|
|
235
|
+
if (
|
|
236
|
+
typeof operation.text !== 'string' ||
|
|
237
|
+
operation.text.length === 0 ||
|
|
238
|
+
operation.text.length > MAX_TEXT_LENGTH
|
|
239
|
+
) {
|
|
240
|
+
throw new Error('Literal text must contain 1 to 256 characters')
|
|
241
|
+
}
|
|
242
|
+
if (/[\u0000-\u001f\u007f-\u009f]/u.test(operation.text)) {
|
|
243
|
+
throw new Error('Literal text must contain printable characters only')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function assertExactFields(operation, allowedFields) {
|
|
248
|
+
const allowed = new Set(allowedFields)
|
|
249
|
+
const unexpected = Object.keys(operation).find((field) => !allowed.has(field))
|
|
250
|
+
if (unexpected) {
|
|
251
|
+
throw new Error(`Unexpected field for ${operation.operation}: ${unexpected}`)
|
|
252
|
+
}
|
|
253
|
+
const missing = allowedFields.find((field) => !Object.hasOwn(operation, field))
|
|
254
|
+
if (missing) {
|
|
255
|
+
throw new Error(`Missing field for ${operation.operation}: ${missing}`)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function sendMove(layout, operation) {
|
|
260
|
+
sendPointerEnterAndMove(layout.webContents, {
|
|
261
|
+
x: Math.floor((operation.x * layout.width) / CANONICAL_WIDTH),
|
|
262
|
+
y: Math.floor((operation.y * layout.height) / CANONICAL_HEIGHT),
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function sendClick(layout, operation) {
|
|
267
|
+
const point = {
|
|
268
|
+
x: Math.floor((operation.x * layout.width) / CANONICAL_WIDTH),
|
|
269
|
+
y: Math.floor((operation.y * layout.height) / CANONICAL_HEIGHT),
|
|
270
|
+
button: operation.button,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Match poi-plugin-kancolle-assistant's long-running browser click sequence.
|
|
274
|
+
layout.webContents.sendInputEvent({ type: 'mouseMove', ...point })
|
|
275
|
+
layout.webContents.sendInputEvent({
|
|
276
|
+
type: 'mouseDown',
|
|
277
|
+
...point,
|
|
278
|
+
clickCount: 3,
|
|
279
|
+
})
|
|
280
|
+
layout.webContents.sendInputEvent({ type: 'mouseUp', ...point, clickCount: 3 })
|
|
281
|
+
layout.webContents.sendInputEvent({
|
|
282
|
+
type: 'mouseMove',
|
|
283
|
+
x: 0,
|
|
284
|
+
y: 0,
|
|
285
|
+
button: operation.button,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function sendDrag(layout, operation, delay) {
|
|
290
|
+
const start = {
|
|
291
|
+
x: Math.floor((operation.fromX * layout.width) / CANONICAL_WIDTH),
|
|
292
|
+
y: Math.floor((operation.fromY * layout.height) / CANONICAL_HEIGHT),
|
|
293
|
+
}
|
|
294
|
+
const destination = {
|
|
295
|
+
x: Math.floor((operation.toX * layout.width) / CANONICAL_WIDTH),
|
|
296
|
+
y: Math.floor((operation.toY * layout.height) / CANONICAL_HEIGHT),
|
|
297
|
+
}
|
|
298
|
+
const mouseButton = { button: operation.button }
|
|
299
|
+
|
|
300
|
+
sendPointerEnterAndMove(layout.webContents, start)
|
|
301
|
+
try {
|
|
302
|
+
await layout.webContents.sendInputEvent({
|
|
303
|
+
type: 'mouseDown',
|
|
304
|
+
...start,
|
|
305
|
+
...mouseButton,
|
|
306
|
+
clickCount: 1,
|
|
307
|
+
})
|
|
308
|
+
let previousX = start.x
|
|
309
|
+
let previousY = start.y
|
|
310
|
+
for (let step = 1; step <= DRAG_MOVE_STEPS; step += 1) {
|
|
311
|
+
const elapsed = Math.round((operation.durationMs * step) / DRAG_MOVE_STEPS)
|
|
312
|
+
const previousElapsed = Math.round(
|
|
313
|
+
(operation.durationMs * (step - 1)) / DRAG_MOVE_STEPS,
|
|
314
|
+
)
|
|
315
|
+
await delay(elapsed - previousElapsed)
|
|
316
|
+
const x = Math.round(
|
|
317
|
+
start.x + ((destination.x - start.x) * step) / DRAG_MOVE_STEPS,
|
|
318
|
+
)
|
|
319
|
+
const y = Math.round(
|
|
320
|
+
start.y + ((destination.y - start.y) * step) / DRAG_MOVE_STEPS,
|
|
321
|
+
)
|
|
322
|
+
await layout.webContents.sendInputEvent({
|
|
323
|
+
type: 'mouseMove',
|
|
324
|
+
x,
|
|
325
|
+
y,
|
|
326
|
+
movementX: x - previousX,
|
|
327
|
+
movementY: y - previousY,
|
|
328
|
+
...mouseButton,
|
|
329
|
+
})
|
|
330
|
+
previousX = x
|
|
331
|
+
previousY = y
|
|
332
|
+
}
|
|
333
|
+
// The game's drag targets (e.g. the organization fleet tab combine drag)
|
|
334
|
+
// require a short hold at the destination before the release; releasing
|
|
335
|
+
// in the same tick as the last move drops the gesture (verified against
|
|
336
|
+
// poi-plugin-kancolle-assistant's mouseMove drag implementation).
|
|
337
|
+
await delay(DRAG_RELEASE_DELAY_MS)
|
|
338
|
+
} finally {
|
|
339
|
+
await layout.webContents.sendInputEvent({
|
|
340
|
+
type: 'mouseUp',
|
|
341
|
+
...destination,
|
|
342
|
+
...mouseButton,
|
|
343
|
+
clickCount: 1,
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function sendPointerEnterAndMove(webContents, point) {
|
|
349
|
+
webContents.sendInputEvent({ type: 'mouseEnter', x: point.x, y: point.y })
|
|
350
|
+
webContents.sendInputEvent({ type: 'mouseMove', x: point.x, y: point.y })
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function defaultDelay(milliseconds) {
|
|
354
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function defaultGetStore(path) {
|
|
358
|
+
if (typeof window !== 'undefined' && typeof window.getStore === 'function') {
|
|
359
|
+
return window.getStore(path)
|
|
360
|
+
}
|
|
361
|
+
return null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function defaultResolveWebContents(webContentsId) {
|
|
365
|
+
const { webContents } = require('@electron/remote')
|
|
366
|
+
return webContents.fromId(webContentsId)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
module.exports = {
|
|
370
|
+
CANONICAL_HEIGHT,
|
|
371
|
+
CANONICAL_WIDTH,
|
|
372
|
+
MAX_TEXT_LENGTH,
|
|
373
|
+
createPoiInputProvider,
|
|
374
|
+
}
|