poi-plugin-mcp 0.2.16 → 0.2.21
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 +366 -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 +3 -3
package/lib/poi-input.js
CHANGED
|
@@ -1,302 +1,366 @@
|
|
|
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
|
-
webContents
|
|
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
|
-
operation.
|
|
149
|
-
operation.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
throw new Error(
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
operation.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
throw new Error('
|
|
170
|
-
}
|
|
171
|
-
|
|
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
|
-
if (
|
|
211
|
-
throw new Error(`
|
|
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
|
-
const
|
|
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
|
-
function
|
|
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
|
+
// The game's PIXI interaction manager drops synthetic pointer events while
|
|
36
|
+
// its document lacks focus (verified live 2026-08-17: clicks reached the
|
|
37
|
+
// canvas yet produced no game/API effect until a real OS-level click gave
|
|
38
|
+
// the WebView focus). Focus the hosting window first: webContents.focus()
|
|
39
|
+
// alone cannot give the document OS focus while the window is backgrounded.
|
|
40
|
+
focusGameWindow(layout.webContents)
|
|
41
|
+
|
|
42
|
+
switch (operation.operation) {
|
|
43
|
+
case 'move':
|
|
44
|
+
validateMove(operation)
|
|
45
|
+
sendMove(layout, operation)
|
|
46
|
+
return 'move'
|
|
47
|
+
case 'click':
|
|
48
|
+
validateClick(operation)
|
|
49
|
+
sendClick(layout, operation)
|
|
50
|
+
return 'click'
|
|
51
|
+
case 'drag':
|
|
52
|
+
validateDrag(operation)
|
|
53
|
+
await sendDrag(layout, operation, delay)
|
|
54
|
+
return 'drag'
|
|
55
|
+
case 'key':
|
|
56
|
+
validateKey(operation)
|
|
57
|
+
await layout.webContents.sendInputEvent({
|
|
58
|
+
type: operation.event,
|
|
59
|
+
keyCode: operation.key,
|
|
60
|
+
})
|
|
61
|
+
return 'key'
|
|
62
|
+
case 'text':
|
|
63
|
+
validateText(operation)
|
|
64
|
+
for (const character of operation.text) {
|
|
65
|
+
await layout.webContents.sendInputEvent({
|
|
66
|
+
type: 'char',
|
|
67
|
+
keyCode: character,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
return 'text'
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unsupported input operation: ${String(operation.operation)}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateOperationObject(operation) {
|
|
78
|
+
if (
|
|
79
|
+
!operation ||
|
|
80
|
+
typeof operation !== 'object' ||
|
|
81
|
+
Array.isArray(operation)
|
|
82
|
+
) {
|
|
83
|
+
throw new Error('Input must be one operation object')
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function focusGameWindow(webContents) {
|
|
88
|
+
try {
|
|
89
|
+
const { BrowserWindow } = require('electron')
|
|
90
|
+
const win = BrowserWindow.fromWebContents(
|
|
91
|
+
webContents.hostWebContents || webContents,
|
|
92
|
+
)
|
|
93
|
+
if (win && !win.isFocused()) {
|
|
94
|
+
// show-no-activate keeps the user's foreground window; focus() then
|
|
95
|
+
// grants the document the OS focus the game's input layer requires.
|
|
96
|
+
if (win.isMinimized()) win.restore()
|
|
97
|
+
win.focus()
|
|
98
|
+
}
|
|
99
|
+
} catch (_windowError) {
|
|
100
|
+
// Fall through to webContents-level focus below.
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
if (typeof webContents.focus === 'function') webContents.focus()
|
|
104
|
+
} catch (_focusError) {
|
|
105
|
+
// Input below is still delivered and may work without focus.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readLayout(getStore, resolveWebContents) {
|
|
110
|
+
const layout = getStore('layout.webview')
|
|
111
|
+
if (!layout || !layout.ref) {
|
|
112
|
+
throw new Error('Poi game WebView is not ready')
|
|
113
|
+
}
|
|
114
|
+
if (
|
|
115
|
+
!Number.isFinite(layout.width) ||
|
|
116
|
+
layout.width <= 0 ||
|
|
117
|
+
!Number.isFinite(layout.height) ||
|
|
118
|
+
layout.height <= 0
|
|
119
|
+
) {
|
|
120
|
+
throw new Error('Poi game WebView dimensions must be positive finite numbers')
|
|
121
|
+
}
|
|
122
|
+
let webContents
|
|
123
|
+
if (typeof layout.ref.getWebContents === 'function') {
|
|
124
|
+
webContents = layout.ref.getWebContents()
|
|
125
|
+
} else if (typeof layout.ref.getWebContentsId === 'function') {
|
|
126
|
+
const webContentsId = layout.ref.getWebContentsId()
|
|
127
|
+
if (!Number.isInteger(webContentsId) || webContentsId <= 0) {
|
|
128
|
+
throw new Error('Poi game WebContents id is invalid')
|
|
129
|
+
}
|
|
130
|
+
webContents = resolveWebContents(webContentsId)
|
|
131
|
+
} else {
|
|
132
|
+
throw new Error('Poi game WebView is not ready')
|
|
133
|
+
}
|
|
134
|
+
if (!webContents || typeof webContents.sendInputEvent !== 'function') {
|
|
135
|
+
throw new Error('Poi game WebContents is not ready')
|
|
136
|
+
}
|
|
137
|
+
return { ...layout, webContents }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function validateClick(operation) {
|
|
141
|
+
assertExactFields(operation, ['operation', 'x', 'y', 'button'])
|
|
142
|
+
if (!Number.isFinite(operation.x) || !Number.isFinite(operation.y)) {
|
|
143
|
+
throw new Error('Click coordinates must be finite numbers')
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
operation.x < 0 ||
|
|
147
|
+
operation.x >= CANONICAL_WIDTH ||
|
|
148
|
+
operation.y < 0 ||
|
|
149
|
+
operation.y >= CANONICAL_HEIGHT
|
|
150
|
+
) {
|
|
151
|
+
throw new Error('Click coordinates must be within canonical bounds')
|
|
152
|
+
}
|
|
153
|
+
if (!SUPPORTED_BUTTONS.has(operation.button)) {
|
|
154
|
+
throw new Error(`Unsupported mouse button: ${String(operation.button)}`)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validateMove(operation) {
|
|
159
|
+
assertExactFields(operation, ['operation', 'x', 'y'])
|
|
160
|
+
if (!Number.isFinite(operation.x) || !Number.isFinite(operation.y)) {
|
|
161
|
+
throw new Error('Move coordinates must be finite numbers')
|
|
162
|
+
}
|
|
163
|
+
if (
|
|
164
|
+
operation.x < 0 ||
|
|
165
|
+
operation.x >= CANONICAL_WIDTH ||
|
|
166
|
+
operation.y < 0 ||
|
|
167
|
+
operation.y >= CANONICAL_HEIGHT
|
|
168
|
+
) {
|
|
169
|
+
throw new Error('Move coordinates must be within canonical bounds')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validateDrag(operation) {
|
|
174
|
+
assertExactFields(operation, [
|
|
175
|
+
'operation',
|
|
176
|
+
'fromX',
|
|
177
|
+
'fromY',
|
|
178
|
+
'toX',
|
|
179
|
+
'toY',
|
|
180
|
+
'durationMs',
|
|
181
|
+
'button',
|
|
182
|
+
])
|
|
183
|
+
const coordinates = [
|
|
184
|
+
operation.fromX,
|
|
185
|
+
operation.fromY,
|
|
186
|
+
operation.toX,
|
|
187
|
+
operation.toY,
|
|
188
|
+
]
|
|
189
|
+
if (coordinates.some((coordinate) => !Number.isFinite(coordinate))) {
|
|
190
|
+
throw new Error('Drag coordinates must be finite numbers')
|
|
191
|
+
}
|
|
192
|
+
if (
|
|
193
|
+
operation.fromX < 0 ||
|
|
194
|
+
operation.fromX >= CANONICAL_WIDTH ||
|
|
195
|
+
operation.toX < 0 ||
|
|
196
|
+
operation.toX >= CANONICAL_WIDTH ||
|
|
197
|
+
operation.fromY < 0 ||
|
|
198
|
+
operation.fromY >= CANONICAL_HEIGHT ||
|
|
199
|
+
operation.toY < 0 ||
|
|
200
|
+
operation.toY >= CANONICAL_HEIGHT
|
|
201
|
+
) {
|
|
202
|
+
throw new Error('Drag coordinates must be within canonical bounds')
|
|
203
|
+
}
|
|
204
|
+
if (!Number.isInteger(operation.durationMs)) {
|
|
205
|
+
throw new Error('Drag durationMs must be an integer')
|
|
206
|
+
}
|
|
207
|
+
if (operation.durationMs < 50 || operation.durationMs > 2000) {
|
|
208
|
+
throw new Error('Drag durationMs must be from 50 to 2000')
|
|
209
|
+
}
|
|
210
|
+
if (!SUPPORTED_BUTTONS.has(operation.button)) {
|
|
211
|
+
throw new Error(`Unsupported mouse button: ${String(operation.button)}`)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function validateKey(operation) {
|
|
216
|
+
assertExactFields(operation, ['operation', 'event', 'key'])
|
|
217
|
+
if (!SUPPORTED_KEY_EVENTS.has(operation.event)) {
|
|
218
|
+
throw new Error(`Unsupported key event: ${String(operation.event)}`)
|
|
219
|
+
}
|
|
220
|
+
if (!SUPPORTED_KEYS.has(operation.key)) {
|
|
221
|
+
throw new Error(`Unsupported key: ${String(operation.key)}`)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function validateText(operation) {
|
|
226
|
+
assertExactFields(operation, ['operation', 'text'])
|
|
227
|
+
if (
|
|
228
|
+
typeof operation.text !== 'string' ||
|
|
229
|
+
operation.text.length === 0 ||
|
|
230
|
+
operation.text.length > MAX_TEXT_LENGTH
|
|
231
|
+
) {
|
|
232
|
+
throw new Error('Literal text must contain 1 to 256 characters')
|
|
233
|
+
}
|
|
234
|
+
if (/[\u0000-\u001f\u007f-\u009f]/u.test(operation.text)) {
|
|
235
|
+
throw new Error('Literal text must contain printable characters only')
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function assertExactFields(operation, allowedFields) {
|
|
240
|
+
const allowed = new Set(allowedFields)
|
|
241
|
+
const unexpected = Object.keys(operation).find((field) => !allowed.has(field))
|
|
242
|
+
if (unexpected) {
|
|
243
|
+
throw new Error(`Unexpected field for ${operation.operation}: ${unexpected}`)
|
|
244
|
+
}
|
|
245
|
+
const missing = allowedFields.find((field) => !Object.hasOwn(operation, field))
|
|
246
|
+
if (missing) {
|
|
247
|
+
throw new Error(`Missing field for ${operation.operation}: ${missing}`)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sendMove(layout, operation) {
|
|
252
|
+
sendPointerEnterAndMove(layout.webContents, {
|
|
253
|
+
x: Math.floor((operation.x * layout.width) / CANONICAL_WIDTH),
|
|
254
|
+
y: Math.floor((operation.y * layout.height) / CANONICAL_HEIGHT),
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function sendClick(layout, operation) {
|
|
259
|
+
const point = {
|
|
260
|
+
x: Math.floor((operation.x * layout.width) / CANONICAL_WIDTH),
|
|
261
|
+
y: Math.floor((operation.y * layout.height) / CANONICAL_HEIGHT),
|
|
262
|
+
button: operation.button,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Match poi-plugin-kancolle-assistant's long-running browser click sequence.
|
|
266
|
+
layout.webContents.sendInputEvent({ type: 'mouseMove', ...point })
|
|
267
|
+
layout.webContents.sendInputEvent({
|
|
268
|
+
type: 'mouseDown',
|
|
269
|
+
...point,
|
|
270
|
+
clickCount: 3,
|
|
271
|
+
})
|
|
272
|
+
layout.webContents.sendInputEvent({ type: 'mouseUp', ...point, clickCount: 3 })
|
|
273
|
+
layout.webContents.sendInputEvent({
|
|
274
|
+
type: 'mouseMove',
|
|
275
|
+
x: 0,
|
|
276
|
+
y: 0,
|
|
277
|
+
button: operation.button,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function sendDrag(layout, operation, delay) {
|
|
282
|
+
const start = {
|
|
283
|
+
x: Math.floor((operation.fromX * layout.width) / CANONICAL_WIDTH),
|
|
284
|
+
y: Math.floor((operation.fromY * layout.height) / CANONICAL_HEIGHT),
|
|
285
|
+
}
|
|
286
|
+
const destination = {
|
|
287
|
+
x: Math.floor((operation.toX * layout.width) / CANONICAL_WIDTH),
|
|
288
|
+
y: Math.floor((operation.toY * layout.height) / CANONICAL_HEIGHT),
|
|
289
|
+
}
|
|
290
|
+
const mouseButton = { button: operation.button }
|
|
291
|
+
|
|
292
|
+
sendPointerEnterAndMove(layout.webContents, start)
|
|
293
|
+
try {
|
|
294
|
+
await layout.webContents.sendInputEvent({
|
|
295
|
+
type: 'mouseDown',
|
|
296
|
+
...start,
|
|
297
|
+
...mouseButton,
|
|
298
|
+
clickCount: 1,
|
|
299
|
+
})
|
|
300
|
+
let previousX = start.x
|
|
301
|
+
let previousY = start.y
|
|
302
|
+
for (let step = 1; step <= DRAG_MOVE_STEPS; step += 1) {
|
|
303
|
+
const elapsed = Math.round((operation.durationMs * step) / DRAG_MOVE_STEPS)
|
|
304
|
+
const previousElapsed = Math.round(
|
|
305
|
+
(operation.durationMs * (step - 1)) / DRAG_MOVE_STEPS,
|
|
306
|
+
)
|
|
307
|
+
await delay(elapsed - previousElapsed)
|
|
308
|
+
const x = Math.round(
|
|
309
|
+
start.x + ((destination.x - start.x) * step) / DRAG_MOVE_STEPS,
|
|
310
|
+
)
|
|
311
|
+
const y = Math.round(
|
|
312
|
+
start.y + ((destination.y - start.y) * step) / DRAG_MOVE_STEPS,
|
|
313
|
+
)
|
|
314
|
+
await layout.webContents.sendInputEvent({
|
|
315
|
+
type: 'mouseMove',
|
|
316
|
+
x,
|
|
317
|
+
y,
|
|
318
|
+
movementX: x - previousX,
|
|
319
|
+
movementY: y - previousY,
|
|
320
|
+
...mouseButton,
|
|
321
|
+
})
|
|
322
|
+
previousX = x
|
|
323
|
+
previousY = y
|
|
324
|
+
}
|
|
325
|
+
// The game's drag targets (e.g. the organization fleet tab combine drag)
|
|
326
|
+
// require a short hold at the destination before the release; releasing
|
|
327
|
+
// in the same tick as the last move drops the gesture (verified against
|
|
328
|
+
// poi-plugin-kancolle-assistant's mouseMove drag implementation).
|
|
329
|
+
await delay(DRAG_RELEASE_DELAY_MS)
|
|
330
|
+
} finally {
|
|
331
|
+
await layout.webContents.sendInputEvent({
|
|
332
|
+
type: 'mouseUp',
|
|
333
|
+
...destination,
|
|
334
|
+
...mouseButton,
|
|
335
|
+
clickCount: 1,
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function sendPointerEnterAndMove(webContents, point) {
|
|
341
|
+
webContents.sendInputEvent({ type: 'mouseEnter', x: point.x, y: point.y })
|
|
342
|
+
webContents.sendInputEvent({ type: 'mouseMove', x: point.x, y: point.y })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function defaultDelay(milliseconds) {
|
|
346
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function defaultGetStore(path) {
|
|
350
|
+
if (typeof window !== 'undefined' && typeof window.getStore === 'function') {
|
|
351
|
+
return window.getStore(path)
|
|
352
|
+
}
|
|
353
|
+
return null
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function defaultResolveWebContents(webContentsId) {
|
|
357
|
+
const { webContents } = require('@electron/remote')
|
|
358
|
+
return webContents.fromId(webContentsId)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = {
|
|
362
|
+
CANONICAL_HEIGHT,
|
|
363
|
+
CANONICAL_WIDTH,
|
|
364
|
+
MAX_TEXT_LENGTH,
|
|
365
|
+
createPoiInputProvider,
|
|
366
|
+
}
|