ogi-addon 1.2.2 → 1.3.1
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 +181 -72
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +59 -17
- package/build/main.d.ts +59 -17
- package/build/main.js +181 -72
- 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 +382 -139
- 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: string | string[];
|
|
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,28 +256,31 @@ export interface OGIAddonConfiguration {
|
|
|
153
256
|
|
|
154
257
|
author: string;
|
|
155
258
|
repository: string;
|
|
259
|
+
storefronts: string[];
|
|
156
260
|
}
|
|
157
261
|
|
|
158
262
|
/**
|
|
159
263
|
* 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
|
|
264
|
+
* @example
|
|
161
265
|
* ```typescript
|
|
162
266
|
* const addon = new OGIAddon({
|
|
163
267
|
* name: 'Test Addon',
|
|
164
|
-
* id: 'test-addon',
|
|
268
|
+
* id: 'test-addon',
|
|
165
269
|
* description: 'A test addon',
|
|
166
270
|
* version: '1.0.0',
|
|
167
271
|
* author: 'OGI Developers',
|
|
168
272
|
* repository: ''
|
|
169
273
|
* });
|
|
170
274
|
* ```
|
|
171
|
-
*
|
|
275
|
+
*
|
|
172
276
|
*/
|
|
173
277
|
export default class OGIAddon {
|
|
174
278
|
public eventEmitter = new events.EventEmitter();
|
|
175
279
|
public addonWSListener: OGIAddonWSListener;
|
|
176
280
|
public addonInfo: OGIAddonConfiguration;
|
|
177
281
|
public config: Configuration = new Configuration({});
|
|
282
|
+
private eventsAvailable: OGIAddonEvent[] = [];
|
|
283
|
+
private registeredConnectEvent: boolean = false;
|
|
178
284
|
|
|
179
285
|
constructor(addonInfo: OGIAddonConfiguration) {
|
|
180
286
|
this.addonInfo = addonInfo;
|
|
@@ -182,20 +288,37 @@ export default class OGIAddon {
|
|
|
182
288
|
}
|
|
183
289
|
|
|
184
290
|
/**
|
|
185
|
-
* Register an event listener for the addon. (See EventListenerTypes)
|
|
291
|
+
* Register an event listener for the addon. (See EventListenerTypes)
|
|
186
292
|
* @param event {OGIAddonEvent}
|
|
187
|
-
* @param listener {EventListenerTypes[OGIAddonEvent]}
|
|
293
|
+
* @param listener {EventListenerTypes[OGIAddonEvent]}
|
|
188
294
|
*/
|
|
189
|
-
public on<T extends OGIAddonEvent>(
|
|
295
|
+
public on<T extends OGIAddonEvent>(
|
|
296
|
+
event: T,
|
|
297
|
+
listener: EventListenerTypes[T]
|
|
298
|
+
) {
|
|
190
299
|
this.eventEmitter.on(event, listener);
|
|
300
|
+
this.eventsAvailable.push(event);
|
|
301
|
+
// wait for the addon to be connected
|
|
302
|
+
if (!this.registeredConnectEvent) {
|
|
303
|
+
this.addonWSListener.eventEmitter.once('connect', () => {
|
|
304
|
+
this.addonWSListener.send('flag', {
|
|
305
|
+
flag: 'events-available',
|
|
306
|
+
value: this.eventsAvailable,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
this.registeredConnectEvent = true;
|
|
310
|
+
}
|
|
191
311
|
}
|
|
192
312
|
|
|
193
|
-
public emit<T extends OGIAddonEvent>(
|
|
313
|
+
public emit<T extends OGIAddonEvent>(
|
|
314
|
+
event: T,
|
|
315
|
+
...args: Parameters<EventListenerTypes[T]>
|
|
316
|
+
) {
|
|
194
317
|
this.eventEmitter.emit(event, ...args);
|
|
195
318
|
}
|
|
196
319
|
|
|
197
320
|
/**
|
|
198
|
-
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
321
|
+
* Notify the client using a notification. Provide the type of notification, the message, and an ID.
|
|
199
322
|
* @param notification {Notification}
|
|
200
323
|
*/
|
|
201
324
|
public notify(notification: Notification) {
|
|
@@ -203,13 +326,19 @@ export default class OGIAddon {
|
|
|
203
326
|
}
|
|
204
327
|
|
|
205
328
|
/**
|
|
206
|
-
*
|
|
207
|
-
* @param
|
|
208
|
-
* @param
|
|
329
|
+
* Get the app details for a given appID and storefront.
|
|
330
|
+
* @param appID {number}
|
|
331
|
+
* @param storefront {string}
|
|
332
|
+
* @returns {Promise<StoreData>}
|
|
209
333
|
*/
|
|
210
|
-
public async
|
|
211
|
-
const id = this.addonWSListener.send('
|
|
212
|
-
|
|
334
|
+
public async getAppDetails(appID: number, storefront: string) {
|
|
335
|
+
const id = this.addonWSListener.send('get-app-details', {
|
|
336
|
+
appID,
|
|
337
|
+
storefront,
|
|
338
|
+
});
|
|
339
|
+
return await this.addonWSListener.waitForResponseFromServer<
|
|
340
|
+
StoreData | undefined
|
|
341
|
+
>(id);
|
|
213
342
|
}
|
|
214
343
|
|
|
215
344
|
/**
|
|
@@ -223,7 +352,13 @@ export default class OGIAddon {
|
|
|
223
352
|
const progress = 0;
|
|
224
353
|
const logs: string[] = [];
|
|
225
354
|
const task = new CustomTask(this.addonWSListener, id, progress, logs);
|
|
226
|
-
this.addonWSListener.send('task-update', {
|
|
355
|
+
this.addonWSListener.send('task-update', {
|
|
356
|
+
id,
|
|
357
|
+
progress,
|
|
358
|
+
logs,
|
|
359
|
+
finished: false,
|
|
360
|
+
failed: undefined,
|
|
361
|
+
});
|
|
227
362
|
return task;
|
|
228
363
|
}
|
|
229
364
|
}
|
|
@@ -235,7 +370,12 @@ export class CustomTask {
|
|
|
235
370
|
public finished: boolean = false;
|
|
236
371
|
public ws: OGIAddonWSListener;
|
|
237
372
|
public failed: string | undefined = undefined;
|
|
238
|
-
constructor(
|
|
373
|
+
constructor(
|
|
374
|
+
ws: OGIAddonWSListener,
|
|
375
|
+
id: string,
|
|
376
|
+
progress: number,
|
|
377
|
+
logs: string[]
|
|
378
|
+
) {
|
|
239
379
|
this.id = id;
|
|
240
380
|
this.progress = progress;
|
|
241
381
|
this.logs = logs;
|
|
@@ -258,7 +398,13 @@ export class CustomTask {
|
|
|
258
398
|
this.update();
|
|
259
399
|
}
|
|
260
400
|
public update() {
|
|
261
|
-
this.ws.send('task-update', {
|
|
401
|
+
this.ws.send('task-update', {
|
|
402
|
+
id: this.id,
|
|
403
|
+
progress: this.progress,
|
|
404
|
+
logs: this.logs,
|
|
405
|
+
finished: this.finished,
|
|
406
|
+
failed: this.failed,
|
|
407
|
+
});
|
|
262
408
|
}
|
|
263
409
|
}
|
|
264
410
|
/**
|
|
@@ -271,17 +417,27 @@ export class CustomTask {
|
|
|
271
417
|
*/
|
|
272
418
|
export class SearchTool<T> {
|
|
273
419
|
private fuse: Fuse<T>;
|
|
274
|
-
constructor(
|
|
420
|
+
constructor(
|
|
421
|
+
items: T[],
|
|
422
|
+
keys: string[],
|
|
423
|
+
options: Omit<IFuseOptions<T>, 'keys'> = {
|
|
424
|
+
threshold: 0.3,
|
|
425
|
+
includeScore: true,
|
|
426
|
+
}
|
|
427
|
+
) {
|
|
275
428
|
this.fuse = new Fuse(items, {
|
|
276
429
|
keys,
|
|
277
|
-
...options
|
|
430
|
+
...options,
|
|
278
431
|
});
|
|
279
432
|
}
|
|
280
433
|
public search(query: string, limit: number = 10): T[] {
|
|
281
|
-
return this.fuse
|
|
434
|
+
return this.fuse
|
|
435
|
+
.search(query)
|
|
436
|
+
.slice(0, limit)
|
|
437
|
+
.map((result) => result.item);
|
|
282
438
|
}
|
|
283
439
|
public addItems(items: T[]) {
|
|
284
|
-
items.map(item => this.fuse.add(item));
|
|
440
|
+
items.map((item) => this.fuse.add(item));
|
|
285
441
|
}
|
|
286
442
|
}
|
|
287
443
|
/**
|
|
@@ -295,7 +451,7 @@ export interface LibraryInfo {
|
|
|
295
451
|
launchExecutable: string;
|
|
296
452
|
launchArguments?: string;
|
|
297
453
|
capsuleImage: string;
|
|
298
|
-
storefront:
|
|
454
|
+
storefront: string;
|
|
299
455
|
addonsource: string;
|
|
300
456
|
coverImage: string;
|
|
301
457
|
titleImage?: string;
|
|
@@ -303,7 +459,7 @@ export interface LibraryInfo {
|
|
|
303
459
|
interface Notification {
|
|
304
460
|
type: 'warning' | 'error' | 'info' | 'success';
|
|
305
461
|
message: string;
|
|
306
|
-
id: string
|
|
462
|
+
id: string;
|
|
307
463
|
}
|
|
308
464
|
class OGIAddonWSListener {
|
|
309
465
|
private socket: WebSocket;
|
|
@@ -311,8 +467,12 @@ class OGIAddonWSListener {
|
|
|
311
467
|
public addon: OGIAddon;
|
|
312
468
|
|
|
313
469
|
constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {
|
|
314
|
-
if (
|
|
315
|
-
|
|
470
|
+
if (
|
|
471
|
+
process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'
|
|
472
|
+
) {
|
|
473
|
+
throw new Error(
|
|
474
|
+
'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'
|
|
475
|
+
);
|
|
316
476
|
}
|
|
317
477
|
this.addon = ogiAddon;
|
|
318
478
|
this.eventEmitter = eventEmitter;
|
|
@@ -325,7 +485,7 @@ class OGIAddonWSListener {
|
|
|
325
485
|
this.send('authenticate', {
|
|
326
486
|
...this.addon.addonInfo,
|
|
327
487
|
secret: process.argv[process.argv.length - 1].split('=')[1],
|
|
328
|
-
ogiVersion: VERSION
|
|
488
|
+
ogiVersion: VERSION,
|
|
329
489
|
});
|
|
330
490
|
|
|
331
491
|
this.eventEmitter.emit('connect');
|
|
@@ -339,10 +499,12 @@ class OGIAddonWSListener {
|
|
|
339
499
|
|
|
340
500
|
this.socket.on('error', (error) => {
|
|
341
501
|
if (error.message.includes('Failed to connect')) {
|
|
342
|
-
throw new Error(
|
|
502
|
+
throw new Error(
|
|
503
|
+
'OGI Addon Server is not running/is unreachable. Please start the server and try again.'
|
|
504
|
+
);
|
|
343
505
|
}
|
|
344
506
|
console.error('An error occurred:', error);
|
|
345
|
-
})
|
|
507
|
+
});
|
|
346
508
|
|
|
347
509
|
this.socket.on('close', (code, reason) => {
|
|
348
510
|
if (code === 1008) {
|
|
@@ -350,8 +512,8 @@ class OGIAddonWSListener {
|
|
|
350
512
|
return;
|
|
351
513
|
}
|
|
352
514
|
this.eventEmitter.emit('disconnect', reason);
|
|
353
|
-
console.log(
|
|
354
|
-
console.error(reason.toString())
|
|
515
|
+
console.log('Disconnected from OGI Addon Server');
|
|
516
|
+
console.error(reason.toString());
|
|
355
517
|
this.eventEmitter.emit('exit');
|
|
356
518
|
this.socket.close();
|
|
357
519
|
});
|
|
@@ -359,21 +521,28 @@ class OGIAddonWSListener {
|
|
|
359
521
|
this.registerMessageReceiver();
|
|
360
522
|
}
|
|
361
523
|
|
|
362
|
-
private async userInputAsked(
|
|
524
|
+
private async userInputAsked(
|
|
525
|
+
configBuilt: ConfigurationBuilder,
|
|
526
|
+
name: string,
|
|
527
|
+
description: string,
|
|
528
|
+
socket: WebSocket
|
|
529
|
+
): Promise<{ [key: string]: number | boolean | string }> {
|
|
363
530
|
const config = configBuilt.build(false);
|
|
364
531
|
const id = Math.random().toString(36).substring(7);
|
|
365
532
|
if (!socket) {
|
|
366
533
|
return {};
|
|
367
534
|
}
|
|
368
|
-
socket.send(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
535
|
+
socket.send(
|
|
536
|
+
JSON.stringify({
|
|
537
|
+
event: 'input-asked',
|
|
538
|
+
args: {
|
|
539
|
+
config,
|
|
540
|
+
name,
|
|
541
|
+
description,
|
|
542
|
+
},
|
|
543
|
+
id: id,
|
|
544
|
+
})
|
|
545
|
+
);
|
|
377
546
|
return await this.waitForResponseFromServer(id);
|
|
378
547
|
}
|
|
379
548
|
|
|
@@ -384,21 +553,42 @@ class OGIAddonWSListener {
|
|
|
384
553
|
case 'config-update':
|
|
385
554
|
const result = this.addon.config.updateConfig(message.args);
|
|
386
555
|
if (!result[0]) {
|
|
387
|
-
this.respondToMessage(message.id!!, {
|
|
388
|
-
|
|
389
|
-
|
|
556
|
+
this.respondToMessage(message.id!!, {
|
|
557
|
+
success: false,
|
|
558
|
+
error: result[1],
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
390
561
|
this.respondToMessage(message.id!!, { success: true });
|
|
391
562
|
}
|
|
392
|
-
break
|
|
563
|
+
break;
|
|
393
564
|
case 'search':
|
|
394
|
-
let searchResultEvent = new EventResponse<SearchResult[]>(
|
|
565
|
+
let searchResultEvent = new EventResponse<SearchResult[]>(
|
|
566
|
+
(screen, name, description) =>
|
|
567
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
568
|
+
);
|
|
395
569
|
this.eventEmitter.emit('search', message.args, searchResultEvent);
|
|
396
|
-
const searchResult =
|
|
570
|
+
const searchResult =
|
|
571
|
+
await this.waitForEventToRespond(searchResultEvent);
|
|
397
572
|
this.respondToMessage(message.id!!, searchResult.data);
|
|
398
|
-
break
|
|
573
|
+
break;
|
|
399
574
|
case 'setup':
|
|
400
|
-
let setupEvent = new EventResponse<LibraryInfo>(
|
|
401
|
-
|
|
575
|
+
let setupEvent = new EventResponse<LibraryInfo>(
|
|
576
|
+
(screen, name, description) =>
|
|
577
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
578
|
+
);
|
|
579
|
+
this.eventEmitter.emit(
|
|
580
|
+
'setup',
|
|
581
|
+
{
|
|
582
|
+
path: message.args.path,
|
|
583
|
+
appID: message.args.appID,
|
|
584
|
+
storefront: message.args.storefront,
|
|
585
|
+
type: message.args.type,
|
|
586
|
+
name: message.args.name,
|
|
587
|
+
usedRealDebrid: message.args.usedRealDebrid,
|
|
588
|
+
multiPartFiles: message.args.multiPartFiles,
|
|
589
|
+
},
|
|
590
|
+
setupEvent
|
|
591
|
+
);
|
|
402
592
|
const interval = setInterval(() => {
|
|
403
593
|
if (setupEvent.resolved) {
|
|
404
594
|
clearInterval(interval);
|
|
@@ -408,54 +598,104 @@ class OGIAddonWSListener {
|
|
|
408
598
|
logs: setupEvent.logs,
|
|
409
599
|
deferID: message.args.deferID,
|
|
410
600
|
progress: setupEvent.progress,
|
|
411
|
-
failed: setupEvent.failed
|
|
601
|
+
failed: setupEvent.failed,
|
|
412
602
|
} as ClientSentEventTypes['defer-update']);
|
|
413
603
|
}, 100);
|
|
414
604
|
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
415
605
|
this.respondToMessage(message.id!!, setupResult.data);
|
|
416
|
-
break
|
|
606
|
+
break;
|
|
417
607
|
case 'library-search':
|
|
418
|
-
let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
|
|
608
|
+
let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
|
|
609
|
+
(screen, name, description) =>
|
|
610
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
611
|
+
);
|
|
419
612
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
420
613
|
this.respondToMessage(message.id!!, []);
|
|
421
614
|
break;
|
|
422
615
|
}
|
|
423
|
-
this.eventEmitter.emit(
|
|
424
|
-
|
|
616
|
+
this.eventEmitter.emit(
|
|
617
|
+
'library-search',
|
|
618
|
+
message.args,
|
|
619
|
+
librarySearchEvent
|
|
620
|
+
);
|
|
621
|
+
const librarySearchResult =
|
|
622
|
+
await this.waitForEventToRespond(librarySearchEvent);
|
|
425
623
|
this.respondToMessage(message.id!!, librarySearchResult.data);
|
|
426
|
-
break
|
|
624
|
+
break;
|
|
427
625
|
case 'game-details':
|
|
428
|
-
let gameDetailsEvent = new EventResponse<StoreData
|
|
626
|
+
let gameDetailsEvent = new EventResponse<StoreData | undefined>(
|
|
627
|
+
(screen, name, description) =>
|
|
628
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
629
|
+
);
|
|
429
630
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
430
|
-
this.respondToMessage(message.id!!, {
|
|
631
|
+
this.respondToMessage(message.id!!, {
|
|
632
|
+
error: 'No event listener for game-details',
|
|
633
|
+
});
|
|
431
634
|
break;
|
|
432
635
|
}
|
|
433
|
-
this.eventEmitter.emit(
|
|
434
|
-
|
|
636
|
+
this.eventEmitter.emit(
|
|
637
|
+
'game-details',
|
|
638
|
+
message.args,
|
|
639
|
+
gameDetailsEvent
|
|
640
|
+
);
|
|
641
|
+
const gameDetailsResult =
|
|
642
|
+
await this.waitForEventToRespond(gameDetailsEvent);
|
|
435
643
|
this.respondToMessage(message.id!!, gameDetailsResult.data);
|
|
436
|
-
break
|
|
644
|
+
break;
|
|
437
645
|
case 'request-dl':
|
|
438
|
-
let requestDLEvent = new EventResponse<SearchResult>(
|
|
646
|
+
let requestDLEvent = new EventResponse<SearchResult>(
|
|
647
|
+
(screen, name, description) =>
|
|
648
|
+
this.userInputAsked(screen, name, description, this.socket)
|
|
649
|
+
);
|
|
439
650
|
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
440
|
-
this.respondToMessage(message.id!!, {
|
|
651
|
+
this.respondToMessage(message.id!!, {
|
|
652
|
+
error: 'No event listener for request-dl',
|
|
653
|
+
});
|
|
441
654
|
break;
|
|
442
655
|
}
|
|
443
|
-
this.eventEmitter.emit(
|
|
444
|
-
|
|
656
|
+
this.eventEmitter.emit(
|
|
657
|
+
'request-dl',
|
|
658
|
+
message.args.appID,
|
|
659
|
+
message.args.info,
|
|
660
|
+
requestDLEvent
|
|
661
|
+
);
|
|
662
|
+
const requestDLResult =
|
|
663
|
+
await this.waitForEventToRespond(requestDLEvent);
|
|
445
664
|
if (requestDLEvent.failed) {
|
|
446
|
-
this.respondToMessage(message.id!!, {
|
|
665
|
+
this.respondToMessage(message.id!!, {
|
|
666
|
+
statusError: requestDLEvent.failed,
|
|
667
|
+
});
|
|
447
668
|
break;
|
|
448
669
|
}
|
|
449
|
-
if (
|
|
450
|
-
|
|
670
|
+
if (
|
|
671
|
+
requestDLEvent.data === undefined ||
|
|
672
|
+
requestDLEvent.data?.downloadType === 'request'
|
|
673
|
+
) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
|
|
676
|
+
);
|
|
451
677
|
}
|
|
452
678
|
this.respondToMessage(message.id!!, requestDLResult.data);
|
|
453
|
-
break
|
|
679
|
+
break;
|
|
680
|
+
case 'catalog':
|
|
681
|
+
let catalogEvent = new EventResponse<{
|
|
682
|
+
[key: string]: {
|
|
683
|
+
name: string;
|
|
684
|
+
description: string;
|
|
685
|
+
listings: BasicLibraryInfo[];
|
|
686
|
+
};
|
|
687
|
+
}>();
|
|
688
|
+
this.eventEmitter.emit('catalog', catalogEvent);
|
|
689
|
+
const catalogResult = await this.waitForEventToRespond(catalogEvent);
|
|
690
|
+
this.respondToMessage(message.id!!, catalogResult.data);
|
|
691
|
+
break;
|
|
454
692
|
}
|
|
455
693
|
});
|
|
456
694
|
}
|
|
457
695
|
|
|
458
|
-
private waitForEventToRespond<T>(
|
|
696
|
+
private waitForEventToRespond<T>(
|
|
697
|
+
event: EventResponse<T>
|
|
698
|
+
): Promise<EventResponse<T>> {
|
|
459
699
|
// check the handlers to see if there even is any
|
|
460
700
|
return new Promise((resolve, reject) => {
|
|
461
701
|
const dataGet = setInterval(() => {
|
|
@@ -474,21 +714,22 @@ class OGIAddonWSListener {
|
|
|
474
714
|
resolve(event);
|
|
475
715
|
}
|
|
476
716
|
}, 100);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
717
|
+
} else {
|
|
479
718
|
reject('Event did not respond in time');
|
|
480
719
|
}
|
|
481
|
-
}, 5000)
|
|
720
|
+
}, 5000);
|
|
482
721
|
});
|
|
483
722
|
}
|
|
484
723
|
|
|
485
724
|
public respondToMessage(messageID: string, response: any) {
|
|
486
|
-
this.socket.send(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
725
|
+
this.socket.send(
|
|
726
|
+
JSON.stringify({
|
|
727
|
+
event: 'response',
|
|
728
|
+
id: messageID,
|
|
729
|
+
args: response,
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
console.log('dispatched response to ' + messageID);
|
|
492
733
|
}
|
|
493
734
|
|
|
494
735
|
public waitForResponseFromServer<T>(messageID: string): Promise<T> {
|
|
@@ -499,33 +740,35 @@ class OGIAddonWSListener {
|
|
|
499
740
|
this.socket.once('message', waiter);
|
|
500
741
|
return;
|
|
501
742
|
}
|
|
502
|
-
console.log(
|
|
743
|
+
console.log('received response from ' + messageID);
|
|
503
744
|
|
|
504
745
|
if (message.id === messageID) {
|
|
505
746
|
resolve(message.args);
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
747
|
+
} else {
|
|
508
748
|
this.socket.once('message', waiter);
|
|
509
749
|
}
|
|
510
|
-
}
|
|
750
|
+
};
|
|
511
751
|
this.socket.once('message', waiter);
|
|
512
752
|
});
|
|
513
753
|
}
|
|
514
754
|
|
|
515
|
-
public send(
|
|
755
|
+
public send(
|
|
756
|
+
event: OGIAddonClientSentEvent,
|
|
757
|
+
args: ClientSentEventTypes[OGIAddonClientSentEvent]
|
|
758
|
+
): string {
|
|
516
759
|
// generate a random id
|
|
517
760
|
const id = Math.random().toString(36).substring(7);
|
|
518
|
-
this.socket.send(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
761
|
+
this.socket.send(
|
|
762
|
+
JSON.stringify({
|
|
763
|
+
event,
|
|
764
|
+
args,
|
|
765
|
+
id,
|
|
766
|
+
})
|
|
767
|
+
);
|
|
523
768
|
return id;
|
|
524
769
|
}
|
|
525
770
|
|
|
526
771
|
public close() {
|
|
527
772
|
this.socket.close();
|
|
528
773
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
774
|
}
|