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