ogi-addon 1.2.2 → 1.3.0
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 +15 -7
- package/build/EventResponse.cjs +3 -3
- package/build/EventResponse.cjs.map +1 -1
- package/build/EventResponse.js +3 -3
- package/build/EventResponse.js.map +1 -1
- package/build/SearchEngine.cjs.map +1 -1
- package/build/SearchEngine.d.cts +1 -0
- package/build/SearchEngine.d.ts +1 -0
- package/build/config/Configuration.cjs +37 -22
- package/build/config/Configuration.cjs.map +1 -1
- package/build/config/Configuration.js +37 -22
- package/build/config/Configuration.js.map +1 -1
- package/build/config/ConfigurationBuilder.cjs +28 -19
- package/build/config/ConfigurationBuilder.cjs.map +1 -1
- package/build/config/ConfigurationBuilder.d.cts +2 -2
- package/build/config/ConfigurationBuilder.d.ts +2 -2
- package/build/config/ConfigurationBuilder.js +28 -19
- package/build/config/ConfigurationBuilder.js.map +1 -1
- package/build/main.cjs +177 -73
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +59 -18
- package/build/main.d.ts +59 -18
- package/build/main.js +177 -73
- package/build/main.js.map +1 -1
- package/package.json +3 -2
- package/schema.json +4 -1
- package/src/EventResponse.ts +22 -11
- package/src/SearchEngine.ts +2 -2
- package/src/config/Configuration.ts +53 -13
- package/src/config/ConfigurationBuilder.ts +79 -56
- package/src/main.ts +381 -141
- package/tsconfig.json +4 -10
- package/tsup.config.js +2 -2
package/src/main.ts
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
import ws, { WebSocket } from 'ws';
|
|
2
2
|
import events from 'node:events';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ConfigurationBuilder,
|
|
5
|
+
ConfigurationFile,
|
|
6
|
+
} from './config/ConfigurationBuilder';
|
|
4
7
|
import { Configuration } from './config/Configuration';
|
|
5
8
|
import EventResponse from './EventResponse';
|
|
6
9
|
import { SearchResult } from './SearchEngine';
|
|
7
10
|
import Fuse, { IFuseOptions } from 'fuse.js';
|
|
8
11
|
|
|
9
|
-
export type OGIAddonEvent =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
export type OGIAddonEvent =
|
|
13
|
+
| 'connect'
|
|
14
|
+
| 'disconnect'
|
|
15
|
+
| 'configure'
|
|
16
|
+
| 'authenticate'
|
|
17
|
+
| 'search'
|
|
18
|
+
| 'setup'
|
|
19
|
+
| 'library-search'
|
|
20
|
+
| 'game-details'
|
|
21
|
+
| 'exit'
|
|
22
|
+
| 'request-dl'
|
|
23
|
+
| 'catalog';
|
|
24
|
+
|
|
25
|
+
export type OGIAddonClientSentEvent =
|
|
26
|
+
| 'response'
|
|
27
|
+
| 'authenticate'
|
|
28
|
+
| 'configure'
|
|
29
|
+
| 'defer-update'
|
|
30
|
+
| 'notification'
|
|
31
|
+
| 'input-asked'
|
|
32
|
+
| 'get-app-details'
|
|
33
|
+
| 'flag'
|
|
34
|
+
| 'task-update';
|
|
35
|
+
|
|
36
|
+
export type OGIAddonServerSentEvent =
|
|
37
|
+
| 'authenticate'
|
|
38
|
+
| 'configure'
|
|
39
|
+
| 'config-update'
|
|
40
|
+
| 'search'
|
|
41
|
+
| 'setup'
|
|
42
|
+
| 'response'
|
|
43
|
+
| 'library-search'
|
|
44
|
+
| 'game-details'
|
|
45
|
+
| 'request-dl'
|
|
46
|
+
| 'catalog';
|
|
13
47
|
export { ConfigurationBuilder, Configuration, EventResponse, SearchResult };
|
|
14
48
|
const defaultPort = 7654;
|
|
15
49
|
import pjson from '../package.json';
|
|
@@ -26,15 +60,11 @@ export interface ClientSentEventTypes {
|
|
|
26
60
|
};
|
|
27
61
|
configure: ConfigurationFile;
|
|
28
62
|
'defer-update': {
|
|
29
|
-
logs: string[]
|
|
30
|
-
progress: number
|
|
63
|
+
logs: string[];
|
|
64
|
+
progress: number;
|
|
31
65
|
};
|
|
32
66
|
notification: Notification;
|
|
33
67
|
'input-asked': ConfigurationBuilder;
|
|
34
|
-
'steam-search': {
|
|
35
|
-
query: string;
|
|
36
|
-
strict: boolean;
|
|
37
|
-
};
|
|
38
68
|
'task-update': {
|
|
39
69
|
id: string;
|
|
40
70
|
progress: number;
|
|
@@ -42,85 +72,153 @@ export interface ClientSentEventTypes {
|
|
|
42
72
|
finished: boolean;
|
|
43
73
|
failed: string | undefined;
|
|
44
74
|
};
|
|
75
|
+
'get-app-details': {
|
|
76
|
+
appID: number;
|
|
77
|
+
storefront: string;
|
|
78
|
+
};
|
|
79
|
+
flag: {
|
|
80
|
+
flag: string;
|
|
81
|
+
value: boolean;
|
|
82
|
+
};
|
|
45
83
|
}
|
|
46
84
|
|
|
47
85
|
export type BasicLibraryInfo = {
|
|
48
86
|
name: string;
|
|
49
87
|
capsuleImage: string;
|
|
50
88
|
appID: number;
|
|
51
|
-
|
|
89
|
+
storefront: string;
|
|
90
|
+
};
|
|
52
91
|
|
|
53
92
|
export interface EventListenerTypes {
|
|
54
93
|
/**
|
|
55
94
|
* This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.
|
|
56
|
-
* @param socket
|
|
57
|
-
* @returns
|
|
95
|
+
* @param socket
|
|
96
|
+
* @returns
|
|
58
97
|
*/
|
|
59
98
|
connect: (socket: ws) => void;
|
|
60
99
|
|
|
61
100
|
/**
|
|
62
101
|
* This event is emitted when the client requests for the addon to disconnect. Addon does not need to resolve this event, but we recommend `process.exit(0)` so the addon can exit gracefully instead of by force by the addon server.
|
|
63
|
-
* @param reason
|
|
64
|
-
* @returns
|
|
102
|
+
* @param reason
|
|
103
|
+
* @returns
|
|
65
104
|
*/
|
|
66
105
|
disconnect: (reason: string) => void;
|
|
67
106
|
/**
|
|
68
|
-
* This event is emitted when the client requests for the addon to configure itself. Addon should resolve the event with the internal configuration. (See ConfigurationBuilder)
|
|
69
|
-
* @param config
|
|
70
|
-
* @returns
|
|
107
|
+
* This event is emitted when the client requests for the addon to configure itself. Addon should resolve the event with the internal configuration. (See ConfigurationBuilder)
|
|
108
|
+
* @param config
|
|
109
|
+
* @returns
|
|
71
110
|
*/
|
|
72
111
|
configure: (config: ConfigurationBuilder) => ConfigurationBuilder;
|
|
73
112
|
/**
|
|
74
|
-
* This event is called when the client provides a response to any event. This should be treated as middleware.
|
|
75
|
-
* @param response
|
|
76
|
-
* @returns
|
|
113
|
+
* This event is called when the client provides a response to any event. This should be treated as middleware.
|
|
114
|
+
* @param response
|
|
115
|
+
* @returns
|
|
77
116
|
*/
|
|
78
117
|
response: (response: any) => void;
|
|
79
118
|
|
|
80
119
|
/**
|
|
81
|
-
* This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
|
|
82
|
-
* @param config
|
|
83
|
-
* @returns
|
|
120
|
+
* This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.
|
|
121
|
+
* @param config
|
|
122
|
+
* @returns
|
|
84
123
|
*/
|
|
85
124
|
authenticate: (config: any) => void;
|
|
86
125
|
/**
|
|
87
|
-
* This event is emitted when the client requests for a torrent/direct download search to be performed. Addon is given the gameID (could be a steam appID or custom store appID), along with the storefront type. Addon should resolve the event with the search results. (See SearchResult)
|
|
88
|
-
* @param query
|
|
89
|
-
* @param event
|
|
90
|
-
* @returns
|
|
126
|
+
* This event is emitted when the client requests for a torrent/direct download search to be performed. Addon is given the gameID (could be a steam appID or custom store appID), along with the storefront type. Addon should resolve the event with the search results. (See SearchResult)
|
|
127
|
+
* @param query
|
|
128
|
+
* @param event
|
|
129
|
+
* @returns
|
|
91
130
|
*/
|
|
92
|
-
search: (
|
|
131
|
+
search: (
|
|
132
|
+
query: { storefront: string; appID: number },
|
|
133
|
+
event: EventResponse<SearchResult[]>
|
|
134
|
+
) => void;
|
|
93
135
|
/**
|
|
94
136
|
* This event is emitted when the client requests for app setup to be performed. Addon should resolve the event with the metadata for the library entry. (See LibraryInfo)
|
|
95
|
-
* @param data
|
|
96
|
-
* @param event
|
|
97
|
-
* @returns
|
|
137
|
+
* @param data
|
|
138
|
+
* @param event
|
|
139
|
+
* @returns
|
|
98
140
|
*/
|
|
99
141
|
setup: (
|
|
100
142
|
data: {
|
|
101
|
-
path: string
|
|
102
|
-
type: 'direct' | 'torrent' | 'magnet'
|
|
103
|
-
name: string
|
|
104
|
-
usedRealDebrid: boolean
|
|
143
|
+
path: string;
|
|
144
|
+
type: 'direct' | 'torrent' | 'magnet';
|
|
145
|
+
name: string;
|
|
146
|
+
usedRealDebrid: boolean;
|
|
105
147
|
multiPartFiles?: {
|
|
106
|
-
name: string
|
|
107
|
-
downloadURL: string
|
|
108
|
-
}[]
|
|
109
|
-
appID: number
|
|
110
|
-
storefront:
|
|
111
|
-
},
|
|
148
|
+
name: string;
|
|
149
|
+
downloadURL: string;
|
|
150
|
+
}[];
|
|
151
|
+
appID: number;
|
|
152
|
+
storefront: string;
|
|
153
|
+
},
|
|
154
|
+
event: EventResponse<
|
|
155
|
+
Omit<
|
|
156
|
+
LibraryInfo,
|
|
157
|
+
| 'capsuleImage'
|
|
158
|
+
| 'coverImage'
|
|
159
|
+
| 'name'
|
|
160
|
+
| 'appID'
|
|
161
|
+
| 'storefront'
|
|
162
|
+
| 'addonsource'
|
|
163
|
+
>
|
|
164
|
+
>
|
|
112
165
|
) => void;
|
|
113
166
|
|
|
114
167
|
/**
|
|
115
|
-
* This event is emitted when the client requires for a search to be performed. Input is the search query.
|
|
116
|
-
* @param query
|
|
117
|
-
* @param event
|
|
118
|
-
* @returns
|
|
168
|
+
* This event is emitted when the client requires for a search to be performed. Input is the search query.
|
|
169
|
+
* @param query
|
|
170
|
+
* @param event
|
|
171
|
+
* @returns
|
|
172
|
+
*/
|
|
173
|
+
'library-search': (
|
|
174
|
+
query: string,
|
|
175
|
+
event: EventResponse<BasicLibraryInfo[]>
|
|
176
|
+
) => void;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* This event is emitted when the client requests for a game details to be fetched. Addon should resolve the event with the game details. This is used to generate a store page for the game.
|
|
180
|
+
* @param appID
|
|
181
|
+
* @param event
|
|
182
|
+
* @returns
|
|
119
183
|
*/
|
|
120
|
-
'library-search': (query: string, event: EventResponse<BasicLibraryInfo[]>) => void;
|
|
121
184
|
'game-details': (appID: number, event: EventResponse<StoreData>) => void;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* This event is emitted when the client requests for the addon to exit. Use this to perform any cleanup tasks, ending with a `process.exit(0)`.
|
|
188
|
+
* @returns
|
|
189
|
+
*/
|
|
122
190
|
exit: () => void;
|
|
123
|
-
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* This event is emitted when the client requests for a download to be performed with the 'request' type. Addon should resolve the event with a SearchResult containing the actual download info.
|
|
194
|
+
* @param appID
|
|
195
|
+
* @param info
|
|
196
|
+
* @param event
|
|
197
|
+
* @returns
|
|
198
|
+
*/
|
|
199
|
+
'request-dl': (
|
|
200
|
+
appID: number,
|
|
201
|
+
info: SearchResult,
|
|
202
|
+
event: EventResponse<SearchResult>
|
|
203
|
+
) => void;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.
|
|
207
|
+
* @param event
|
|
208
|
+
* @returns
|
|
209
|
+
*/
|
|
210
|
+
catalog: (
|
|
211
|
+
event: Omit<
|
|
212
|
+
EventResponse<{
|
|
213
|
+
[key: string]: {
|
|
214
|
+
name: string;
|
|
215
|
+
description: string;
|
|
216
|
+
listings: BasicLibraryInfo[];
|
|
217
|
+
};
|
|
218
|
+
}>,
|
|
219
|
+
'askForInput'
|
|
220
|
+
>
|
|
221
|
+
) => void;
|
|
124
222
|
}
|
|
125
223
|
|
|
126
224
|
export interface StoreData {
|
|
@@ -145,6 +243,11 @@ export interface WebsocketMessageServer {
|
|
|
145
243
|
id?: string;
|
|
146
244
|
args: any;
|
|
147
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The configuration for the addon. This is used to identify the addon and provide information about it.
|
|
249
|
+
* Storefronts is an array of names of stores that the addon supports.
|
|
250
|
+
*/
|
|
148
251
|
export interface OGIAddonConfiguration {
|
|
149
252
|
name: string;
|
|
150
253
|
id: string;
|
|
@@ -153,22 +256,24 @@ export interface OGIAddonConfiguration {
|
|
|
153
256
|
|
|
154
257
|
author: string;
|
|
155
258
|
repository: string;
|
|
259
|
+
storefronts: string[];
|
|
260
|
+
storeFrontServerCapable: boolean;
|
|
156
261
|
}
|
|
157
262
|
|
|
158
263
|
/**
|
|
159
264
|
* The main class for the OGI Addon. This class is used to interact with the OGI Addon Server. The OGI Addon Server provides a `--addonSecret` to the addon so it can securely connect.
|
|
160
|
-
* @example
|
|
265
|
+
* @example
|
|
161
266
|
* ```typescript
|
|
162
267
|
* const addon = new OGIAddon({
|
|
163
268
|
* name: 'Test Addon',
|
|
164
|
-
* id: 'test-addon',
|
|
269
|
+
* id: 'test-addon',
|
|
165
270
|
* description: 'A test addon',
|
|
166
271
|
* version: '1.0.0',
|
|
167
272
|
* author: 'OGI Developers',
|
|
168
273
|
* repository: ''
|
|
169
274
|
* });
|
|
170
275
|
* ```
|
|
171
|
-
*
|
|
276
|
+
*
|
|
172
277
|
*/
|
|
173
278
|
export default class OGIAddon {
|
|
174
279
|
public eventEmitter = new events.EventEmitter();
|
|
@@ -176,26 +281,41 @@ export default class OGIAddon {
|
|
|
176
281
|
public addonInfo: OGIAddonConfiguration;
|
|
177
282
|
public config: Configuration = new Configuration({});
|
|
178
283
|
|
|
179
|
-
constructor(
|
|
180
|
-
|
|
284
|
+
constructor(
|
|
285
|
+
addonInfo: Omit<OGIAddonConfiguration, 'storeFrontServerCapable'>
|
|
286
|
+
) {
|
|
287
|
+
this.addonInfo = { storeFrontServerCapable: false, ...addonInfo };
|
|
181
288
|
this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);
|
|
182
289
|
}
|
|
183
290
|
|
|
184
291
|
/**
|
|
185
|
-
* Register an event listener for the addon. (See EventListenerTypes)
|
|
292
|
+
* Register an event listener for the addon. (See EventListenerTypes)
|
|
186
293
|
* @param event {OGIAddonEvent}
|
|
187
|
-
* @param listener {EventListenerTypes[OGIAddonEvent]}
|
|
294
|
+
* @param listener {EventListenerTypes[OGIAddonEvent]}
|
|
188
295
|
*/
|
|
189
|
-
public on<T extends OGIAddonEvent>(
|
|
296
|
+
public on<T extends OGIAddonEvent>(
|
|
297
|
+
event: T,
|
|
298
|
+
listener: EventListenerTypes[T]
|
|
299
|
+
) {
|
|
190
300
|
this.eventEmitter.on(event, listener);
|
|
301
|
+
if (event === 'game-details') {
|
|
302
|
+
this.addonInfo.storeFrontServerCapable = true;
|
|
303
|
+
this.addonWSListener.send('flag', {
|
|
304
|
+
flag: 'storeFrontServerCapable',
|
|
305
|
+
value: this.addonInfo.storeFrontServerCapable,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
191
308
|
}
|
|
192
309
|
|
|
193
|
-
public emit<T extends OGIAddonEvent>(
|
|
310
|
+
public emit<T extends OGIAddonEvent>(
|
|
311
|
+
event: T,
|
|
312
|
+
...args: Parameters<EventListenerTypes[T]>
|
|
313
|
+
) {
|
|
194
314
|
this.eventEmitter.emit(event, ...args);
|
|
195
315
|
}
|
|
196
316
|
|
|
197
317
|
/**
|
|
198
|
-
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
318
|
+
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
199
319
|
* @param notification {Notification}
|
|
200
320
|
*/
|
|
201
321
|
public notify(notification: Notification) {
|
|
@@ -203,13 +323,19 @@ export default class OGIAddon {
|
|
|
203
323
|
}
|
|
204
324
|
|
|
205
325
|
/**
|
|
206
|
-
*
|
|
207
|
-
* @param
|
|
208
|
-
* @param
|
|
326
|
+
* Get the app details for a given appID and storefront.
|
|
327
|
+
* @param appID {number}
|
|
328
|
+
* @param storefront {string}
|
|
329
|
+
* @returns {Promise<StoreData>}
|
|
209
330
|
*/
|
|
210
|
-
public async
|
|
211
|
-
const id = this.addonWSListener.send('
|
|
212
|
-
|
|
331
|
+
public async getAppDetails(appID: number, storefront: string) {
|
|
332
|
+
const id = this.addonWSListener.send('get-app-details', {
|
|
333
|
+
appID,
|
|
334
|
+
storefront,
|
|
335
|
+
});
|
|
336
|
+
return await this.addonWSListener.waitForResponseFromServer<
|
|
337
|
+
StoreData | undefined
|
|
338
|
+
>(id);
|
|
213
339
|
}
|
|
214
340
|
|
|
215
341
|
/**
|
|
@@ -223,7 +349,13 @@ export default class OGIAddon {
|
|
|
223
349
|
const progress = 0;
|
|
224
350
|
const logs: string[] = [];
|
|
225
351
|
const task = new CustomTask(this.addonWSListener, id, progress, logs);
|
|
226
|
-
this.addonWSListener.send('task-update', {
|
|
352
|
+
this.addonWSListener.send('task-update', {
|
|
353
|
+
id,
|
|
354
|
+
progress,
|
|
355
|
+
logs,
|
|
356
|
+
finished: false,
|
|
357
|
+
failed: undefined,
|
|
358
|
+
});
|
|
227
359
|
return task;
|
|
228
360
|
}
|
|
229
361
|
}
|
|
@@ -235,7 +367,12 @@ export class CustomTask {
|
|
|
235
367
|
public finished: boolean = false;
|
|
236
368
|
public ws: OGIAddonWSListener;
|
|
237
369
|
public failed: string | undefined = undefined;
|
|
238
|
-
constructor(
|
|
370
|
+
constructor(
|
|
371
|
+
ws: OGIAddonWSListener,
|
|
372
|
+
id: string,
|
|
373
|
+
progress: number,
|
|
374
|
+
logs: string[]
|
|
375
|
+
) {
|
|
239
376
|
this.id = id;
|
|
240
377
|
this.progress = progress;
|
|
241
378
|
this.logs = logs;
|
|
@@ -258,7 +395,13 @@ export class CustomTask {
|
|
|
258
395
|
this.update();
|
|
259
396
|
}
|
|
260
397
|
public update() {
|
|
261
|
-
this.ws.send('task-update', {
|
|
398
|
+
this.ws.send('task-update', {
|
|
399
|
+
id: this.id,
|
|
400
|
+
progress: this.progress,
|
|
401
|
+
logs: this.logs,
|
|
402
|
+
finished: this.finished,
|
|
403
|
+
failed: this.failed,
|
|
404
|
+
});
|
|
262
405
|
}
|
|
263
406
|
}
|
|
264
407
|
/**
|
|
@@ -271,17 +414,27 @@ export class CustomTask {
|
|
|
271
414
|
*/
|
|
272
415
|
export class SearchTool<T> {
|
|
273
416
|
private fuse: Fuse<T>;
|
|
274
|
-
constructor(
|
|
417
|
+
constructor(
|
|
418
|
+
items: T[],
|
|
419
|
+
keys: string[],
|
|
420
|
+
options: Omit<IFuseOptions<T>, 'keys'> = {
|
|
421
|
+
threshold: 0.3,
|
|
422
|
+
includeScore: true,
|
|
423
|
+
}
|
|
424
|
+
) {
|
|
275
425
|
this.fuse = new Fuse(items, {
|
|
276
426
|
keys,
|
|
277
|
-
...options
|
|
427
|
+
...options,
|
|
278
428
|
});
|
|
279
429
|
}
|
|
280
430
|
public search(query: string, limit: number = 10): T[] {
|
|
281
|
-
return this.fuse
|
|
431
|
+
return this.fuse
|
|
432
|
+
.search(query)
|
|
433
|
+
.slice(0, limit)
|
|
434
|
+
.map((result) => result.item);
|
|
282
435
|
}
|
|
283
436
|
public addItems(items: T[]) {
|
|
284
|
-
items.map(item => this.fuse.add(item));
|
|
437
|
+
items.map((item) => this.fuse.add(item));
|
|
285
438
|
}
|
|
286
439
|
}
|
|
287
440
|
/**
|
|
@@ -295,7 +448,7 @@ export interface LibraryInfo {
|
|
|
295
448
|
launchExecutable: string;
|
|
296
449
|
launchArguments?: string;
|
|
297
450
|
capsuleImage: string;
|
|
298
|
-
storefront:
|
|
451
|
+
storefront: string;
|
|
299
452
|
addonsource: string;
|
|
300
453
|
coverImage: string;
|
|
301
454
|
titleImage?: string;
|
|
@@ -303,7 +456,7 @@ export interface LibraryInfo {
|
|
|
303
456
|
interface Notification {
|
|
304
457
|
type: 'warning' | 'error' | 'info' | 'success';
|
|
305
458
|
message: string;
|
|
306
|
-
id: string
|
|
459
|
+
id: string;
|
|
307
460
|
}
|
|
308
461
|
class OGIAddonWSListener {
|
|
309
462
|
private socket: WebSocket;
|
|
@@ -311,8 +464,12 @@ class OGIAddonWSListener {
|
|
|
311
464
|
public addon: OGIAddon;
|
|
312
465
|
|
|
313
466
|
constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {
|
|
314
|
-
if (
|
|
315
|
-
|
|
467
|
+
if (
|
|
468
|
+
process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'
|
|
469
|
+
) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'
|
|
472
|
+
);
|
|
316
473
|
}
|
|
317
474
|
this.addon = ogiAddon;
|
|
318
475
|
this.eventEmitter = eventEmitter;
|
|
@@ -325,7 +482,7 @@ class OGIAddonWSListener {
|
|
|
325
482
|
this.send('authenticate', {
|
|
326
483
|
...this.addon.addonInfo,
|
|
327
484
|
secret: process.argv[process.argv.length - 1].split('=')[1],
|
|
328
|
-
ogiVersion: VERSION
|
|
485
|
+
ogiVersion: VERSION,
|
|
329
486
|
});
|
|
330
487
|
|
|
331
488
|
this.eventEmitter.emit('connect');
|
|
@@ -339,10 +496,12 @@ class OGIAddonWSListener {
|
|
|
339
496
|
|
|
340
497
|
this.socket.on('error', (error) => {
|
|
341
498
|
if (error.message.includes('Failed to connect')) {
|
|
342
|
-
throw new Error(
|
|
499
|
+
throw new Error(
|
|
500
|
+
'OGI Addon Server is not running/is unreachable. Please start the server and try again.'
|
|
501
|
+
);
|
|
343
502
|
}
|
|
344
503
|
console.error('An error occurred:', error);
|
|
345
|
-
})
|
|
504
|
+
});
|
|
346
505
|
|
|
347
506
|
this.socket.on('close', (code, reason) => {
|
|
348
507
|
if (code === 1008) {
|
|
@@ -350,8 +509,8 @@ class OGIAddonWSListener {
|
|
|
350
509
|
return;
|
|
351
510
|
}
|
|
352
511
|
this.eventEmitter.emit('disconnect', reason);
|
|
353
|
-
console.log(
|
|
354
|
-
console.error(reason.toString())
|
|
512
|
+
console.log('Disconnected from OGI Addon Server');
|
|
513
|
+
console.error(reason.toString());
|
|
355
514
|
this.eventEmitter.emit('exit');
|
|
356
515
|
this.socket.close();
|
|
357
516
|
});
|
|
@@ -359,21 +518,28 @@ class OGIAddonWSListener {
|
|
|
359
518
|
this.registerMessageReceiver();
|
|
360
519
|
}
|
|
361
520
|
|
|
362
|
-
private async userInputAsked(
|
|
521
|
+
private async userInputAsked(
|
|
522
|
+
configBuilt: ConfigurationBuilder,
|
|
523
|
+
name: string,
|
|
524
|
+
description: string,
|
|
525
|
+
socket: WebSocket
|
|
526
|
+
): Promise<{ [key: string]: number | boolean | string }> {
|
|
363
527
|
const config = configBuilt.build(false);
|
|
364
528
|
const id = Math.random().toString(36).substring(7);
|
|
365
529
|
if (!socket) {
|
|
366
530
|
return {};
|
|
367
531
|
}
|
|
368
|
-
socket.send(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
532
|
+
socket.send(
|
|
533
|
+
JSON.stringify({
|
|
534
|
+
event: 'input-asked',
|
|
535
|
+
args: {
|
|
536
|
+
config,
|
|
537
|
+
name,
|
|
538
|
+
description,
|
|
539
|
+
},
|
|
540
|
+
id: id,
|
|
541
|
+
})
|
|
542
|
+
);
|
|
377
543
|
return await this.waitForResponseFromServer(id);
|
|
378
544
|
}
|
|
379
545
|
|
|
@@ -384,21 +550,42 @@ class OGIAddonWSListener {
|
|
|
384
550
|
case 'config-update':
|
|
385
551
|
const result = this.addon.config.updateConfig(message.args);
|
|
386
552
|
if (!result[0]) {
|
|
387
|
-
this.respondToMessage(message.id!!, {
|
|
388
|
-
|
|
389
|
-
|
|
553
|
+
this.respondToMessage(message.id!!, {
|
|
554
|
+
success: false,
|
|
555
|
+
error: result[1],
|
|
556
|
+
});
|
|
557
|
+
} else {
|
|
390
558
|
this.respondToMessage(message.id!!, { success: true });
|
|
391
559
|
}
|
|
392
|
-
break
|
|
560
|
+
break;
|
|
393
561
|
case 'search':
|
|
394
|
-
let searchResultEvent = new EventResponse<SearchResult[]>(
|
|
562
|
+
let searchResultEvent = new EventResponse<SearchResult[]>(
|
|
563
|
+
(screen, name, description) =>
|
|
564
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
565
|
+
);
|
|
395
566
|
this.eventEmitter.emit('search', message.args, searchResultEvent);
|
|
396
|
-
const searchResult =
|
|
567
|
+
const searchResult =
|
|
568
|
+
await this.waitForEventToRespond(searchResultEvent);
|
|
397
569
|
this.respondToMessage(message.id!!, searchResult.data);
|
|
398
|
-
break
|
|
570
|
+
break;
|
|
399
571
|
case 'setup':
|
|
400
|
-
let setupEvent = new EventResponse<LibraryInfo>(
|
|
401
|
-
|
|
572
|
+
let setupEvent = new EventResponse<LibraryInfo>(
|
|
573
|
+
(screen, name, description) =>
|
|
574
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
575
|
+
);
|
|
576
|
+
this.eventEmitter.emit(
|
|
577
|
+
'setup',
|
|
578
|
+
{
|
|
579
|
+
path: message.args.path,
|
|
580
|
+
appID: message.args.appID,
|
|
581
|
+
storefront: message.args.storefront,
|
|
582
|
+
type: message.args.type,
|
|
583
|
+
name: message.args.name,
|
|
584
|
+
usedRealDebrid: message.args.usedRealDebrid,
|
|
585
|
+
multiPartFiles: message.args.multiPartFiles,
|
|
586
|
+
},
|
|
587
|
+
setupEvent
|
|
588
|
+
);
|
|
402
589
|
const interval = setInterval(() => {
|
|
403
590
|
if (setupEvent.resolved) {
|
|
404
591
|
clearInterval(interval);
|
|
@@ -408,54 +595,104 @@ class OGIAddonWSListener {
|
|
|
408
595
|
logs: setupEvent.logs,
|
|
409
596
|
deferID: message.args.deferID,
|
|
410
597
|
progress: setupEvent.progress,
|
|
411
|
-
failed: setupEvent.failed
|
|
598
|
+
failed: setupEvent.failed,
|
|
412
599
|
} as ClientSentEventTypes['defer-update']);
|
|
413
600
|
}, 100);
|
|
414
601
|
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
415
602
|
this.respondToMessage(message.id!!, setupResult.data);
|
|
416
|
-
break
|
|
603
|
+
break;
|
|
417
604
|
case 'library-search':
|
|
418
|
-
let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
|
|
605
|
+
let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
|
|
606
|
+
(screen, name, description) =>
|
|
607
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
608
|
+
);
|
|
419
609
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
420
610
|
this.respondToMessage(message.id!!, []);
|
|
421
611
|
break;
|
|
422
612
|
}
|
|
423
|
-
this.eventEmitter.emit(
|
|
424
|
-
|
|
613
|
+
this.eventEmitter.emit(
|
|
614
|
+
'library-search',
|
|
615
|
+
message.args,
|
|
616
|
+
librarySearchEvent
|
|
617
|
+
);
|
|
618
|
+
const librarySearchResult =
|
|
619
|
+
await this.waitForEventToRespond(librarySearchEvent);
|
|
425
620
|
this.respondToMessage(message.id!!, librarySearchResult.data);
|
|
426
|
-
break
|
|
621
|
+
break;
|
|
427
622
|
case 'game-details':
|
|
428
|
-
let gameDetailsEvent = new EventResponse<StoreData
|
|
623
|
+
let gameDetailsEvent = new EventResponse<StoreData | undefined>(
|
|
624
|
+
(screen, name, description) =>
|
|
625
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
626
|
+
);
|
|
429
627
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
430
|
-
this.respondToMessage(message.id!!, {
|
|
628
|
+
this.respondToMessage(message.id!!, {
|
|
629
|
+
error: 'No event listener for game-details',
|
|
630
|
+
});
|
|
431
631
|
break;
|
|
432
632
|
}
|
|
433
|
-
this.eventEmitter.emit(
|
|
434
|
-
|
|
633
|
+
this.eventEmitter.emit(
|
|
634
|
+
'game-details',
|
|
635
|
+
message.args,
|
|
636
|
+
gameDetailsEvent
|
|
637
|
+
);
|
|
638
|
+
const gameDetailsResult =
|
|
639
|
+
await this.waitForEventToRespond(gameDetailsEvent);
|
|
435
640
|
this.respondToMessage(message.id!!, gameDetailsResult.data);
|
|
436
|
-
break
|
|
641
|
+
break;
|
|
437
642
|
case 'request-dl':
|
|
438
|
-
let requestDLEvent = new EventResponse<SearchResult>(
|
|
643
|
+
let requestDLEvent = new EventResponse<SearchResult>(
|
|
644
|
+
(screen, name, description) =>
|
|
645
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
646
|
+
);
|
|
439
647
|
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
440
|
-
this.respondToMessage(message.id!!, {
|
|
648
|
+
this.respondToMessage(message.id!!, {
|
|
649
|
+
error: 'No event listener for request-dl',
|
|
650
|
+
});
|
|
441
651
|
break;
|
|
442
652
|
}
|
|
443
|
-
this.eventEmitter.emit(
|
|
444
|
-
|
|
653
|
+
this.eventEmitter.emit(
|
|
654
|
+
'request-dl',
|
|
655
|
+
message.args.appID,
|
|
656
|
+
message.args.info,
|
|
657
|
+
requestDLEvent
|
|
658
|
+
);
|
|
659
|
+
const requestDLResult =
|
|
660
|
+
await this.waitForEventToRespond(requestDLEvent);
|
|
445
661
|
if (requestDLEvent.failed) {
|
|
446
|
-
this.respondToMessage(message.id!!, {
|
|
662
|
+
this.respondToMessage(message.id!!, {
|
|
663
|
+
statusError: requestDLEvent.failed,
|
|
664
|
+
});
|
|
447
665
|
break;
|
|
448
666
|
}
|
|
449
|
-
if (
|
|
450
|
-
|
|
667
|
+
if (
|
|
668
|
+
requestDLEvent.data === undefined ||
|
|
669
|
+
requestDLEvent.data?.downloadType === 'request'
|
|
670
|
+
) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
|
|
673
|
+
);
|
|
451
674
|
}
|
|
452
675
|
this.respondToMessage(message.id!!, requestDLResult.data);
|
|
453
|
-
break
|
|
676
|
+
break;
|
|
677
|
+
case 'catalog':
|
|
678
|
+
let catalogEvent = new EventResponse<{
|
|
679
|
+
[key: string]: {
|
|
680
|
+
name: string;
|
|
681
|
+
description: string;
|
|
682
|
+
listings: BasicLibraryInfo[];
|
|
683
|
+
};
|
|
684
|
+
}>();
|
|
685
|
+
this.eventEmitter.emit('catalog', catalogEvent);
|
|
686
|
+
const catalogResult = await this.waitForEventToRespond(catalogEvent);
|
|
687
|
+
this.respondToMessage(message.id!!, catalogResult.data);
|
|
688
|
+
break;
|
|
454
689
|
}
|
|
455
690
|
});
|
|
456
691
|
}
|
|
457
692
|
|
|
458
|
-
private waitForEventToRespond<T>(
|
|
693
|
+
private waitForEventToRespond<T>(
|
|
694
|
+
event: EventResponse<T>
|
|
695
|
+
): Promise<EventResponse<T>> {
|
|
459
696
|
// check the handlers to see if there even is any
|
|
460
697
|
return new Promise((resolve, reject) => {
|
|
461
698
|
const dataGet = setInterval(() => {
|
|
@@ -474,21 +711,22 @@ class OGIAddonWSListener {
|
|
|
474
711
|
resolve(event);
|
|
475
712
|
}
|
|
476
713
|
}, 100);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
714
|
+
} else {
|
|
479
715
|
reject('Event did not respond in time');
|
|
480
716
|
}
|
|
481
|
-
}, 5000)
|
|
717
|
+
}, 5000);
|
|
482
718
|
});
|
|
483
719
|
}
|
|
484
720
|
|
|
485
721
|
public respondToMessage(messageID: string, response: any) {
|
|
486
|
-
this.socket.send(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
722
|
+
this.socket.send(
|
|
723
|
+
JSON.stringify({
|
|
724
|
+
event: 'response',
|
|
725
|
+
id: messageID,
|
|
726
|
+
args: response,
|
|
727
|
+
})
|
|
728
|
+
);
|
|
729
|
+
console.log('dispatched response to ' + messageID);
|
|
492
730
|
}
|
|
493
731
|
|
|
494
732
|
public waitForResponseFromServer<T>(messageID: string): Promise<T> {
|
|
@@ -499,33 +737,35 @@ class OGIAddonWSListener {
|
|
|
499
737
|
this.socket.once('message', waiter);
|
|
500
738
|
return;
|
|
501
739
|
}
|
|
502
|
-
console.log(
|
|
740
|
+
console.log('received response from ' + messageID);
|
|
503
741
|
|
|
504
742
|
if (message.id === messageID) {
|
|
505
743
|
resolve(message.args);
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
744
|
+
} else {
|
|
508
745
|
this.socket.once('message', waiter);
|
|
509
746
|
}
|
|
510
|
-
}
|
|
747
|
+
};
|
|
511
748
|
this.socket.once('message', waiter);
|
|
512
749
|
});
|
|
513
750
|
}
|
|
514
751
|
|
|
515
|
-
public send(
|
|
752
|
+
public send(
|
|
753
|
+
event: OGIAddonClientSentEvent,
|
|
754
|
+
args: ClientSentEventTypes[OGIAddonClientSentEvent]
|
|
755
|
+
): string {
|
|
516
756
|
// generate a random id
|
|
517
757
|
const id = Math.random().toString(36).substring(7);
|
|
518
|
-
this.socket.send(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
758
|
+
this.socket.send(
|
|
759
|
+
JSON.stringify({
|
|
760
|
+
event,
|
|
761
|
+
args,
|
|
762
|
+
id,
|
|
763
|
+
})
|
|
764
|
+
);
|
|
523
765
|
return id;
|
|
524
766
|
}
|
|
525
767
|
|
|
526
768
|
public close() {
|
|
527
769
|
this.socket.close();
|
|
528
770
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
771
|
}
|