ogi-addon 1.5.0 → 1.5.2
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/build/SearchEngine.cjs.map +1 -1
- package/build/SearchEngine.d.cts +1 -0
- package/build/SearchEngine.d.ts +1 -0
- package/build/main.cjs +51 -24
- package/build/main.cjs.map +1 -1
- package/build/main.d.cts +1 -1
- package/build/main.d.ts +1 -1
- package/build/main.js +51 -24
- package/build/main.js.map +1 -1
- package/package.json +1 -1
- package/src/SearchEngine.ts +1 -0
- package/src/main.ts +53 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/SearchEngine.ts"],"sourcesContent":["export type SearchResult = {\n name: string;\n downloadType: 'torrent' | 'direct' | 'magnet' | 'request';\n filename?: string;\n downloadURL?: string;\n files?: {\n name: string;\n downloadURL: string;\n }[];\n manifest?: Record<string, any>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/SearchEngine.ts"],"sourcesContent":["export type SearchResult = {\n name: string;\n downloadType: 'torrent' | 'direct' | 'magnet' | 'request';\n filename?: string;\n downloadURL?: string;\n files?: {\n name: string;\n downloadURL: string;\n headers?: Record<string, string>;\n }[];\n manifest?: Record<string, any>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/build/SearchEngine.d.cts
CHANGED
package/build/SearchEngine.d.ts
CHANGED
package/build/main.cjs
CHANGED
|
@@ -418,7 +418,7 @@ var package_default = {
|
|
|
418
418
|
module: "./build/main.js",
|
|
419
419
|
type: "module",
|
|
420
420
|
main: "./build/main.cjs",
|
|
421
|
-
version: "1.5.
|
|
421
|
+
version: "1.5.2",
|
|
422
422
|
exports: {
|
|
423
423
|
".": {
|
|
424
424
|
import: {
|
|
@@ -675,12 +675,16 @@ var OGIAddonWSListener = class {
|
|
|
675
675
|
case "config-update":
|
|
676
676
|
const result = this.addon.config.updateConfig(message.args);
|
|
677
677
|
if (!result[0]) {
|
|
678
|
-
this.respondToMessage(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
678
|
+
this.respondToMessage(
|
|
679
|
+
message.id,
|
|
680
|
+
{
|
|
681
|
+
success: false,
|
|
682
|
+
error: result[1]
|
|
683
|
+
},
|
|
684
|
+
void 0
|
|
685
|
+
);
|
|
682
686
|
} else {
|
|
683
|
-
this.respondToMessage(message.id, { success: true });
|
|
687
|
+
this.respondToMessage(message.id, { success: true }, void 0);
|
|
684
688
|
}
|
|
685
689
|
break;
|
|
686
690
|
case "search":
|
|
@@ -689,7 +693,11 @@ var OGIAddonWSListener = class {
|
|
|
689
693
|
);
|
|
690
694
|
this.eventEmitter.emit("search", message.args, searchResultEvent);
|
|
691
695
|
const searchResult = await this.waitForEventToRespond(searchResultEvent);
|
|
692
|
-
this.respondToMessage(
|
|
696
|
+
this.respondToMessage(
|
|
697
|
+
message.id,
|
|
698
|
+
searchResult.data,
|
|
699
|
+
searchResultEvent
|
|
700
|
+
);
|
|
693
701
|
break;
|
|
694
702
|
case "setup":
|
|
695
703
|
let setupEvent = new EventResponse(
|
|
@@ -721,14 +729,14 @@ var OGIAddonWSListener = class {
|
|
|
721
729
|
});
|
|
722
730
|
}, 100);
|
|
723
731
|
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
724
|
-
this.respondToMessage(message.id, setupResult.data);
|
|
732
|
+
this.respondToMessage(message.id, setupResult.data, setupEvent);
|
|
725
733
|
break;
|
|
726
734
|
case "library-search":
|
|
727
735
|
let librarySearchEvent = new EventResponse(
|
|
728
736
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
729
737
|
);
|
|
730
738
|
if (this.eventEmitter.listenerCount("game-details") === 0) {
|
|
731
|
-
this.respondToMessage(message.id, []);
|
|
739
|
+
this.respondToMessage(message.id, [], librarySearchEvent);
|
|
732
740
|
break;
|
|
733
741
|
}
|
|
734
742
|
this.eventEmitter.emit(
|
|
@@ -737,16 +745,24 @@ var OGIAddonWSListener = class {
|
|
|
737
745
|
librarySearchEvent
|
|
738
746
|
);
|
|
739
747
|
const librarySearchResult = await this.waitForEventToRespond(librarySearchEvent);
|
|
740
|
-
this.respondToMessage(
|
|
748
|
+
this.respondToMessage(
|
|
749
|
+
message.id,
|
|
750
|
+
librarySearchResult.data,
|
|
751
|
+
librarySearchEvent
|
|
752
|
+
);
|
|
741
753
|
break;
|
|
742
754
|
case "game-details":
|
|
743
755
|
let gameDetailsEvent = new EventResponse(
|
|
744
756
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
745
757
|
);
|
|
746
758
|
if (this.eventEmitter.listenerCount("game-details") === 0) {
|
|
747
|
-
this.respondToMessage(
|
|
748
|
-
|
|
749
|
-
|
|
759
|
+
this.respondToMessage(
|
|
760
|
+
message.id,
|
|
761
|
+
{
|
|
762
|
+
error: "No event listener for game-details"
|
|
763
|
+
},
|
|
764
|
+
gameDetailsEvent
|
|
765
|
+
);
|
|
750
766
|
break;
|
|
751
767
|
}
|
|
752
768
|
this.eventEmitter.emit(
|
|
@@ -755,16 +771,24 @@ var OGIAddonWSListener = class {
|
|
|
755
771
|
gameDetailsEvent
|
|
756
772
|
);
|
|
757
773
|
const gameDetailsResult = await this.waitForEventToRespond(gameDetailsEvent);
|
|
758
|
-
this.respondToMessage(
|
|
774
|
+
this.respondToMessage(
|
|
775
|
+
message.id,
|
|
776
|
+
gameDetailsResult.data,
|
|
777
|
+
gameDetailsEvent
|
|
778
|
+
);
|
|
759
779
|
break;
|
|
760
780
|
case "request-dl":
|
|
761
781
|
let requestDLEvent = new EventResponse(
|
|
762
782
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
763
783
|
);
|
|
764
784
|
if (this.eventEmitter.listenerCount("request-dl") === 0) {
|
|
765
|
-
this.respondToMessage(
|
|
766
|
-
|
|
767
|
-
|
|
785
|
+
this.respondToMessage(
|
|
786
|
+
message.id,
|
|
787
|
+
{
|
|
788
|
+
error: "No event listener for request-dl"
|
|
789
|
+
},
|
|
790
|
+
requestDLEvent
|
|
791
|
+
);
|
|
768
792
|
break;
|
|
769
793
|
}
|
|
770
794
|
this.eventEmitter.emit(
|
|
@@ -775,9 +799,7 @@ var OGIAddonWSListener = class {
|
|
|
775
799
|
);
|
|
776
800
|
const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
|
|
777
801
|
if (requestDLEvent.failed) {
|
|
778
|
-
this.respondToMessage(message.id,
|
|
779
|
-
statusError: requestDLEvent.failed
|
|
780
|
-
});
|
|
802
|
+
this.respondToMessage(message.id, void 0, requestDLEvent);
|
|
781
803
|
break;
|
|
782
804
|
}
|
|
783
805
|
if (requestDLEvent.data === void 0 || requestDLEvent.data?.downloadType === "request") {
|
|
@@ -785,13 +807,17 @@ var OGIAddonWSListener = class {
|
|
|
785
807
|
"Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type."
|
|
786
808
|
);
|
|
787
809
|
}
|
|
788
|
-
this.respondToMessage(
|
|
810
|
+
this.respondToMessage(
|
|
811
|
+
message.id,
|
|
812
|
+
requestDLResult.data,
|
|
813
|
+
requestDLEvent
|
|
814
|
+
);
|
|
789
815
|
break;
|
|
790
816
|
case "catalog":
|
|
791
817
|
let catalogEvent = new EventResponse();
|
|
792
818
|
this.eventEmitter.emit("catalog", catalogEvent);
|
|
793
819
|
const catalogResult = await this.waitForEventToRespond(catalogEvent);
|
|
794
|
-
this.respondToMessage(message.id, catalogResult.data);
|
|
820
|
+
this.respondToMessage(message.id, catalogResult.data, catalogEvent);
|
|
795
821
|
break;
|
|
796
822
|
}
|
|
797
823
|
});
|
|
@@ -819,12 +845,13 @@ var OGIAddonWSListener = class {
|
|
|
819
845
|
}, 5e3);
|
|
820
846
|
});
|
|
821
847
|
}
|
|
822
|
-
respondToMessage(messageID, response) {
|
|
848
|
+
respondToMessage(messageID, response, originalEvent) {
|
|
823
849
|
this.socket.send(
|
|
824
850
|
JSON.stringify({
|
|
825
851
|
event: "response",
|
|
826
852
|
id: messageID,
|
|
827
|
-
args: response
|
|
853
|
+
args: response,
|
|
854
|
+
statusError: originalEvent ? originalEvent.failed : void 0
|
|
828
855
|
})
|
|
829
856
|
);
|
|
830
857
|
console.log("dispatched response to " + messageID);
|
package/build/main.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts","../src/config/ConfigurationBuilder.ts","../src/config/Configuration.ts","../src/EventResponse.ts","../package.json"],"sourcesContent":["import ws, { WebSocket } from 'ws';\nimport events from 'node:events';\nimport {\n ConfigurationBuilder,\n ConfigurationFile,\n} from './config/ConfigurationBuilder';\nimport { Configuration } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport { SearchResult } from './SearchEngine';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\nexport type OGIAddonEvent =\n | 'connect'\n | 'disconnect'\n | 'configure'\n | 'authenticate'\n | 'search'\n | 'setup'\n | 'library-search'\n | 'game-details'\n | 'exit'\n | 'request-dl'\n | 'catalog';\n\nexport type OGIAddonClientSentEvent =\n | 'response'\n | 'authenticate'\n | 'configure'\n | 'defer-update'\n | 'notification'\n | 'input-asked'\n | 'get-app-details'\n | 'flag'\n | 'task-update';\n\nexport type OGIAddonServerSentEvent =\n | 'authenticate'\n | 'configure'\n | 'config-update'\n | 'search'\n | 'setup'\n | 'response'\n | 'library-search'\n | 'game-details'\n | 'request-dl'\n | 'catalog';\nexport { ConfigurationBuilder, Configuration, EventResponse, SearchResult };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nexport const VERSION = pjson.version;\n\nexport interface ClientSentEventTypes {\n response: any;\n authenticate: {\n name: string;\n id: string;\n description: string;\n version: string;\n author: string;\n };\n configure: ConfigurationFile;\n 'defer-update': {\n logs: string[];\n progress: number;\n };\n notification: Notification;\n 'input-asked': ConfigurationBuilder;\n 'task-update': {\n id: string;\n progress: number;\n logs: string[];\n finished: boolean;\n failed: string | undefined;\n };\n 'get-app-details': {\n appID: number;\n storefront: string;\n };\n flag: {\n flag: string;\n value: string | string[];\n };\n}\n\nexport type BasicLibraryInfo = {\n name: string;\n capsuleImage: string;\n appID: number;\n storefront: string;\n};\n\nexport interface EventListenerTypes {\n /**\n * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.\n * @param socket\n * @returns\n */\n connect: (socket: ws) => void;\n\n /**\n * 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.\n * @param reason\n * @returns\n */\n disconnect: (reason: string) => void;\n /**\n * 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)\n * @param config\n * @returns\n */\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n /**\n * This event is called when the client provides a response to any event. This should be treated as middleware.\n * @param response\n * @returns\n */\n response: (response: any) => void;\n\n /**\n * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.\n * @param config\n * @returns\n */\n authenticate: (config: any) => void;\n /**\n * 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)\n * @param query\n * @param event\n * @returns\n */\n search: (\n query: { storefront: string; appID: number },\n event: EventResponse<SearchResult[]>\n ) => void;\n /**\n * 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)\n * @param data\n * @param event\n * @returns\n */\n setup: (\n data: {\n path: string;\n type: 'direct' | 'torrent' | 'magnet';\n name: string;\n usedRealDebrid: boolean;\n multiPartFiles?: {\n name: string;\n downloadURL: string;\n }[];\n appID: number;\n storefront: string;\n },\n event: EventResponse<\n Omit<\n LibraryInfo,\n | 'capsuleImage'\n | 'coverImage'\n | 'name'\n | 'appID'\n | 'storefront'\n | 'addonsource'\n | 'titleImage'\n >\n >\n ) => void;\n\n /**\n * This event is emitted when the client requires for a search to be performed. Input is the search query.\n * @param query\n * @param event\n * @returns\n */\n 'library-search': (\n query: string,\n event: EventResponse<BasicLibraryInfo[]>\n ) => void;\n\n /**\n * 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.\n * @param appID\n * @param event\n * @returns\n */\n 'game-details': (\n details: { appID: number; storefront: string },\n event: EventResponse<StoreData | undefined>\n ) => void;\n\n /**\n * 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)`.\n * @returns\n */\n exit: () => void;\n\n /**\n * 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.\n * @param appID\n * @param info\n * @param event\n * @returns\n */\n 'request-dl': (\n appID: number,\n info: SearchResult,\n event: EventResponse<SearchResult>\n ) => void;\n\n /**\n * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.\n * @param event\n * @returns\n */\n catalog: (\n event: Omit<\n EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>,\n 'askForInput'\n >\n ) => void;\n}\n\nexport interface StoreData {\n name: string;\n publishers: string[];\n developers: string[];\n appID: number;\n releaseDate: string;\n capsuleImage: string;\n coverImage: string;\n basicDescription: string;\n description: string;\n headerImage: string;\n}\nexport interface WebsocketMessageClient {\n event: OGIAddonClientSentEvent;\n id?: string;\n args: any;\n}\nexport interface WebsocketMessageServer {\n event: OGIAddonServerSentEvent;\n id?: string;\n args: any;\n}\n\n/**\n * The configuration for the addon. This is used to identify the addon and provide information about it.\n * Storefronts is an array of names of stores that the addon supports.\n */\nexport interface OGIAddonConfiguration {\n name: string;\n id: string;\n description: string;\n version: string;\n\n author: string;\n repository: string;\n storefronts: string[];\n}\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonEvent[] = [];\n private registeredConnectEvent: boolean = false;\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonEvent}\n * @param listener {EventListenerTypes[OGIAddonEvent]}\n */\n public on<T extends OGIAddonEvent>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonEvent>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: Notification) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n const id = this.addonWSListener.send('get-app-details', {\n appID,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n StoreData | undefined\n >(id);\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @param id {string}\n * @param progress {number}\n * @param logs {string[]}\n */\n public async task() {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new CustomTask(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n}\n\nexport class CustomTask {\n public readonly id: string;\n public progress: number;\n public logs: string[];\n public finished: boolean = false;\n public ws: OGIAddonWSListener;\n public failed: string | undefined = undefined;\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n ) {\n this.id = id;\n this.progress = progress;\n this.logs = logs;\n this.ws = ws;\n }\n public log(log: string) {\n this.logs.push(log);\n this.update();\n }\n public finish() {\n this.finished = true;\n this.update();\n }\n public fail(message: string) {\n this.failed = message;\n this.update();\n }\n public setProgress(progress: number) {\n this.progress = progress;\n this.update();\n }\n public update() {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport interface LibraryInfo {\n name: string;\n version: string;\n cwd: string;\n appID: number;\n launchExecutable: string;\n launchArguments?: string;\n capsuleImage: string;\n storefront: string;\n addonsource: string;\n coverImage: string;\n titleImage?: string;\n}\ninterface Notification {\n type: 'warning' | 'error' | 'info' | 'success';\n message: string;\n id: string;\n}\nclass OGIAddonWSListener {\n private socket: WebSocket;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n if (\n process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'\n ) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n this.socket = new ws('ws://localhost:' + defaultPort);\n this.socket.on('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate', {\n ...this.addon.addonInfo,\n secret: process.argv[process.argv.length - 1].split('=')[1],\n ogiVersion: VERSION,\n });\n\n this.eventEmitter.emit('connect');\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n });\n\n this.socket.on('error', (error) => {\n if (error.message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', error);\n });\n\n this.socket.on('close', (code, reason) => {\n if (code === 1008) {\n console.error('Authentication failed:', reason);\n return;\n }\n this.eventEmitter.emit('disconnect', reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(reason.toString());\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked(\n configBuilt: ConfigurationBuilder,\n name: string,\n description: string,\n socket: WebSocket\n ): Promise<{ [key: string]: number | boolean | string }> {\n const config = configBuilt.build(false);\n const id = Math.random().toString(36).substring(7);\n if (!socket) {\n return {};\n }\n socket.send(\n JSON.stringify({\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n id: id,\n })\n );\n return await this.waitForResponseFromServer(id);\n }\n\n private registerMessageReceiver() {\n this.socket.on('message', async (data: string) => {\n const message: WebsocketMessageServer = JSON.parse(data);\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(message.args);\n if (!result[0]) {\n this.respondToMessage(message.id!!, {\n success: false,\n error: result[1],\n });\n } else {\n this.respondToMessage(message.id!!, { success: true });\n }\n break;\n case 'search':\n let searchResultEvent = new EventResponse<SearchResult[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit('search', message.args, searchResultEvent);\n const searchResult =\n await this.waitForEventToRespond(searchResultEvent);\n this.respondToMessage(message.id!!, searchResult.data);\n break;\n case 'setup':\n let setupEvent = new EventResponse<LibraryInfo>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit(\n 'setup',\n {\n path: message.args.path,\n appID: message.args.appID,\n storefront: message.args.storefront,\n type: message.args.type,\n name: message.args.name,\n usedRealDebrid: message.args.usedRealDebrid,\n multiPartFiles: message.args.multiPartFiles,\n },\n setupEvent\n );\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID: message.args.deferID,\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data);\n break;\n case 'library-search':\n let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, []);\n break;\n }\n this.eventEmitter.emit(\n 'library-search',\n message.args,\n librarySearchEvent\n );\n const librarySearchResult =\n await this.waitForEventToRespond(librarySearchEvent);\n this.respondToMessage(message.id!!, librarySearchResult.data);\n break;\n case 'game-details':\n let gameDetailsEvent = new EventResponse<StoreData | undefined>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, {\n error: 'No event listener for game-details',\n });\n break;\n }\n this.eventEmitter.emit(\n 'game-details',\n message.args,\n gameDetailsEvent\n );\n const gameDetailsResult =\n await this.waitForEventToRespond(gameDetailsEvent);\n this.respondToMessage(message.id!!, gameDetailsResult.data);\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(message.id!!, {\n error: 'No event listener for request-dl',\n });\n break;\n }\n this.eventEmitter.emit(\n 'request-dl',\n message.args.appID,\n message.args.info,\n requestDLEvent\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, {\n statusError: requestDLEvent.failed,\n });\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(message.id!!, requestDLResult.data);\n break;\n case 'catalog':\n let catalogEvent = new EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>();\n this.eventEmitter.emit('catalog', catalogEvent);\n const catalogResult = await this.waitForEventToRespond(catalogEvent);\n this.respondToMessage(message.id!!, catalogResult.data);\n break;\n }\n });\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n public respondToMessage(messageID: string, response: any) {\n this.socket.send(\n JSON.stringify({\n event: 'response',\n id: messageID,\n args: response,\n })\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public waitForResponseFromServer<T>(messageID: string): Promise<T> {\n return new Promise((resolve) => {\n const waiter = (data: string) => {\n const message: WebsocketMessageClient = JSON.parse(data);\n if (message.event !== 'response') {\n this.socket.once('message', waiter);\n return;\n }\n console.log('received response from ' + messageID);\n\n if (message.id === messageID) {\n resolve(message.args);\n } else {\n this.socket.once('message', waiter);\n }\n };\n this.socket.once('message', waiter);\n });\n }\n\n public send(\n event: OGIAddonClientSentEvent,\n args: ClientSentEventTypes[OGIAddonClientSentEvent]\n ): string {\n // generate a random id\n const id = Math.random().toString(36).substring(7);\n this.socket.send(\n JSON.stringify({\n event,\n args,\n id,\n })\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n","import z, { ZodError } from 'zod';\n\nexport interface ConfigurationFile {\n [key: string]: ConfigurationOption;\n}\n\nconst configValidation = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n description: z.string().min(1),\n});\n\nexport function isStringOption(\n option: ConfigurationOption\n): option is StringOption {\n return option.type === 'string';\n}\n\nexport function isNumberOption(\n option: ConfigurationOption\n): option is NumberOption {\n return option.type === 'number';\n}\n\nexport function isBooleanOption(\n option: ConfigurationOption\n): option is BooleanOption {\n return option.type === 'boolean';\n}\n\nexport class ConfigurationBuilder {\n private options: ConfigurationOption[] = [];\n\n /**\n * Add a number option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: NumberOption) => NumberOption }\n * @returns\n */\n public addNumberOption(\n option: (option: NumberOption) => NumberOption\n ): ConfigurationBuilder {\n let newOption = new NumberOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a string option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: StringOption) => StringOption }\n */\n public addStringOption(option: (option: StringOption) => StringOption) {\n let newOption = new StringOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a boolean option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: BooleanOption) => BooleanOption }\n */\n public addBooleanOption(option: (option: BooleanOption) => BooleanOption) {\n let newOption = new BooleanOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n public build(includeFunctions: boolean): ConfigurationFile {\n let config: ConfigurationFile = {};\n this.options.forEach((option) => {\n // remove all functions from the option object\n if (!includeFunctions) {\n option = JSON.parse(JSON.stringify(option));\n const optionData = configValidation.safeParse(option);\n if (!optionData.success) {\n throw new ZodError(optionData.error.errors);\n }\n\n config[option.name] = option;\n } else {\n config[option.name] = option;\n }\n });\n return config;\n }\n}\n\nexport type ConfigurationOptionType = 'string' | 'number' | 'boolean' | 'unset';\nexport class ConfigurationOption {\n public name: string = '';\n public defaultValue: unknown = '';\n public displayName: string = '';\n public description: string = '';\n public type: ConfigurationOptionType = 'unset';\n\n /**\n * Set the name of the option. **REQUIRED**\n * @param name {string} The name of the option. This is used to reference the option in the configuration file.\n */\n setName(name: string) {\n this.name = name;\n return this;\n }\n\n /**\n * Set the display name of the option. This is used to show the user a human readable version of what the option is. **REQUIRED**\n * @param displayName {string} The display name of the option.\n * @returns\n */\n setDisplayName(displayName: string) {\n this.displayName = displayName;\n return this;\n }\n\n /**\n * Set the description of the option. This is to show the user a brief description of what this option does. **REQUIRED**\n * @param description {string} The description of the option.\n * @returns\n */\n setDescription(description: string) {\n this.description = description;\n return this;\n }\n\n /**\n * Validation code for the option. This is called when the user provides input to the option. If the validation fails, the user will be prompted to provide input again.\n * @param input {unknown} The input to validate\n */\n validate(input: unknown): [boolean, string] {\n throw new Error('Validation code not implemented. Value: ' + input);\n }\n}\n\nexport class StringOption extends ConfigurationOption {\n public allowedValues: string[] = [];\n public minTextLength: number = 0;\n public maxTextLength: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: string = '';\n public inputType: 'text' | 'file' | 'password' | 'folder' = 'text';\n public type: ConfigurationOptionType = 'string';\n\n /**\n * Set the allowed values for the string. If the array is empty, any value is allowed. When provided, the client will act like this option is a dropdown.\n * @param allowedValues {string[]} An array of allowed values for the string. If the array is empty, any value is allowed.\n */\n setAllowedValues(allowedValues: string[]): this {\n this.allowedValues = allowedValues;\n return this;\n }\n\n /**\n * Set the default value for the string. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {string} The default value for the string.\n */\n setDefaultValue(defaultValue: string): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n /**\n * Set the minimum text length for the string. If the user provides a string that is less than this value, the validation will fail.\n * @param minTextLength {number} The minimum text length for the string.\n */\n setMinTextLength(minTextLength: number): this {\n this.minTextLength = minTextLength;\n return this;\n }\n\n /**\n * Set the maximum text length for the string. If the user provides a string that is greater than this value, the validation will fail.\n * @param maxTextLength {number} The maximum text length for the string.\n */\n setMaxTextLength(maxTextLength: number): this {\n this.maxTextLength = maxTextLength;\n return this;\n }\n\n /**\n * Set the input type for the string. This will change how the client renders the input.\n * @param inputType {'text' | 'file' | 'password' | 'folder'} The input type for the string.\n */\n setInputType(inputType: 'text' | 'file' | 'password' | 'folder'): this {\n this.inputType = inputType;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'string') {\n return [false, 'Input is not a string'];\n }\n if (this.allowedValues.length === 0 && input.length !== 0)\n return [true, ''];\n if (\n input.length < this.minTextLength ||\n input.length > this.maxTextLength\n ) {\n return [\n false,\n 'Input is not within the text length ' +\n this.minTextLength +\n ' and ' +\n this.maxTextLength +\n ' characters (currently ' +\n input.length +\n ' characters)',\n ];\n }\n\n return [\n this.allowedValues.includes(input),\n 'Input is not an allowed value',\n ];\n }\n}\n\nexport class NumberOption extends ConfigurationOption {\n public min: number = 0;\n public max: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: number = 0;\n public type: ConfigurationOptionType = 'number';\n public inputType: 'range' | 'number' = 'number';\n\n /**\n * Set the minimum value for the number. If the user provides a number that is less than this value, the validation will fail.\n * @param min {number} The minimum value for the number.\n */\n setMin(min: number): this {\n this.min = min;\n return this;\n }\n\n /**\n * Set the input type for the number. This will change how the client renders the input.\n * @param type {'range' | 'number'} The input type for the number.\n */\n setInputType(type: 'range' | 'number'): this {\n this.inputType = type;\n return this;\n }\n\n /**\n * Set the maximum value for the number. If the user provides a number that is greater than this value, the validation will fail.\n * @param max {number} The maximum value for the number.\n */\n setMax(max: number): this {\n this.max = max;\n return this;\n }\n\n /**\n * Set the default value for the number. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {number} The default value for the number.\n */\n setDefaultValue(defaultValue: number): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (isNaN(Number(input))) {\n return [false, 'Input is not a number'];\n }\n if (Number(input) < this.min || Number(input) > this.max) {\n return [\n false,\n 'Input is not within the range of ' + this.min + ' and ' + this.max,\n ];\n }\n return [true, ''];\n }\n}\n\nexport class BooleanOption extends ConfigurationOption {\n public type: ConfigurationOptionType = 'boolean';\n public defaultValue: boolean = false;\n\n /**\n * Set the default value for the boolean. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {boolean} The default value for the boolean.\n */\n setDefaultValue(defaultValue: boolean): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'boolean') {\n return [false, 'Input is not a boolean'];\n }\n return [true, ''];\n }\n}\n","import {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n} from './ConfigurationBuilder';\n\ninterface DefiniteConfig {\n [key: string]: string | number | boolean;\n}\nexport class Configuration {\n readonly storedConfigTemplate: ConfigurationFile;\n definiteConfig: DefiniteConfig = {};\n constructor(configTemplate: ConfigurationFile) {\n this.storedConfigTemplate = configTemplate;\n }\n\n updateConfig(\n config: DefiniteConfig,\n validate: boolean = true\n ): [boolean, { [key: string]: string }] {\n this.definiteConfig = config;\n if (validate) {\n const result = this.validateConfig();\n return result;\n }\n return [true, {}];\n }\n // provides falsey or truthy value, and an error message if falsey\n private validateConfig(): [boolean, { [key: string]: string }] {\n const erroredKeys = new Map<string, string>();\n for (const key in this.storedConfigTemplate) {\n if (\n this.definiteConfig[key] === null ||\n this.definiteConfig[key] === undefined\n ) {\n console.warn(\n 'Option ' +\n key +\n ' is not defined. Using default value Value: ' +\n this.definiteConfig[key]\n );\n this.definiteConfig[key] = this.storedConfigTemplate[key]\n .defaultValue as string | number | boolean;\n }\n if (\n this.storedConfigTemplate[key].type !== typeof this.definiteConfig[key]\n ) {\n throw new Error('Option ' + key + ' is not of the correct type');\n }\n\n const result = this.storedConfigTemplate[key].validate(\n this.definiteConfig[key]\n );\n if (!result[0]) {\n erroredKeys.set(key, result[1]);\n }\n }\n\n for (const key in this.definiteConfig) {\n if (!this.storedConfigTemplate[key]) {\n throw new Error(\n 'Option ' + key + ' is not defined in the configuration template'\n );\n }\n }\n\n if (erroredKeys.size > 0) {\n return [false, Object.fromEntries(erroredKeys)];\n }\n\n return [true, Object.fromEntries(erroredKeys)];\n }\n\n getStringValue(optionName: string): string {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'string') {\n throw new Error('Option ' + optionName + ' is not a string');\n }\n return this.definiteConfig[optionName];\n }\n\n getNumberValue(optionName: string): number {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'number') {\n throw new Error('Option ' + optionName + ' is not a number');\n }\n return this.definiteConfig[optionName];\n }\n\n getBooleanValue(optionName: string): boolean {\n if (this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'boolean') {\n throw new Error('Option ' + optionName + ' is not a boolean');\n }\n return this.definiteConfig[optionName];\n }\n}\n\nexport {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n};\n","import { ConfigurationBuilder } from './main';\n\nexport default class EventResponse<T> {\n data: T | undefined = undefined;\n deffered: boolean = false;\n resolved: boolean = false;\n progress: number = 0;\n logs: string[] = [];\n failed: string | undefined = undefined;\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>;\n\n constructor(\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>\n ) {\n this.onInputAsked = onInputAsked;\n }\n\n public defer(promise?: () => Promise<void>) {\n this.deffered = true;\n // include this to make it easier to use the defer method with async functions\n if (promise) {\n promise();\n }\n }\n\n /**\n * Resolve the event with data. This acts like a promise resolve, and will stop the event from being processed further. **You must always call this method when you are done with the event.**\n * @param data {T}\n */\n public resolve(data: T) {\n this.resolved = true;\n this.data = data;\n }\n\n /**\n * Completes the event and resolves it, but does not return any data. **You must always call this method when you are done with the event.**\n */\n public complete() {\n this.resolved = true;\n }\n\n public fail(message: string) {\n this.resolved = true;\n this.failed = message;\n }\n\n /**\n * Logs a message to the event. This is useful for debugging and logging information to the user.\n * @param message {string}\n */\n public log(message: string) {\n this.logs.push(message);\n }\n\n /**\n * Send a screen to the client to ask for input. Use the `ConfigurationBuilder` system to build the screen. Once sent to the user, the addon cannot change the screen.\n * @async\n * @param name {string}\n * @param description {string}\n * @param screen {ConfigurationBuilder}\n * @returns {Promise<{ [key: string]: boolean | string | number }>}\n */\n public async askForInput(\n name: string,\n description: string,\n screen: ConfigurationBuilder\n ): Promise<{ [key: string]: boolean | string | number }> {\n if (!this.onInputAsked) {\n throw new Error('No input asked callback');\n }\n return await this.onInputAsked(screen, name, description);\n }\n}\n","{\n \"name\": \"ogi-addon\",\n \"module\": \"./build/main.js\",\n \"type\": \"module\",\n \"main\": \"./build/main.cjs\",\n \"version\": \"1.5.0\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"default\": \"./build/main.js\",\n \"types\": \"./build/main.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/main.cjs\",\n \"types\": \"./build/main.d.cts\"\n }\n },\n \"./config\": {\n \"import\": {\n \"default\": \"./build/config/Configuration.js\",\n \"types\": \"./build/config/Configuration.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/config/Configuration.cjs\",\n \"types\": \"./build/config/Configuration.d.cts\"\n }\n }\n },\n \"typings\": \"./build/main.d.ts\",\n \"author\": {\n \"name\": \"Nat3z\",\n \"email\": \"me@nat3z.com\",\n \"url\": \"https://nat3z.com/\"\n },\n \"dependencies\": {\n \"fuse.js\": \"^7.1.0\",\n \"ws\": \"^8.4.0\",\n \"zod\": \"^3.23.8\"\n },\n \"scripts\": {\n \"auto-build\": \"tsc -w\",\n \"build\": \"tsup --config tsup.config.js\",\n \"release\": \"bun run build && npm publish\",\n \"release-beta\": \"bun run build && npm publish --tag future\"\n },\n \"devDependencies\": {\n \"@types/minimatch\": \"^6.0.0\",\n \"@types/node\": \"^20.14.12\",\n \"@types/ws\": \"^8.4.0\",\n \"prettier\": \"^3.6.0\",\n \"tsup\": \"^8.2.3\",\n \"typescript\": \"^5.0.0\"\n }\n}\n"],"mappings":"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","names":["z","events","ws","Fuse"]}
|
|
1
|
+
{"version":3,"sources":["../src/main.ts","../src/config/ConfigurationBuilder.ts","../src/config/Configuration.ts","../src/EventResponse.ts","../package.json"],"sourcesContent":["import ws, { WebSocket } from 'ws';\nimport events from 'node:events';\nimport {\n ConfigurationBuilder,\n ConfigurationFile,\n} from './config/ConfigurationBuilder';\nimport { Configuration } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport { SearchResult } from './SearchEngine';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\nexport type OGIAddonEvent =\n | 'connect'\n | 'disconnect'\n | 'configure'\n | 'authenticate'\n | 'search'\n | 'setup'\n | 'library-search'\n | 'game-details'\n | 'exit'\n | 'request-dl'\n | 'catalog';\n\nexport type OGIAddonClientSentEvent =\n | 'response'\n | 'authenticate'\n | 'configure'\n | 'defer-update'\n | 'notification'\n | 'input-asked'\n | 'get-app-details'\n | 'flag'\n | 'task-update';\n\nexport type OGIAddonServerSentEvent =\n | 'authenticate'\n | 'configure'\n | 'config-update'\n | 'search'\n | 'setup'\n | 'response'\n | 'library-search'\n | 'game-details'\n | 'request-dl'\n | 'catalog';\nexport { ConfigurationBuilder, Configuration, EventResponse, SearchResult };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nexport const VERSION = pjson.version;\n\nexport interface ClientSentEventTypes {\n response: any;\n authenticate: {\n name: string;\n id: string;\n description: string;\n version: string;\n author: string;\n };\n configure: ConfigurationFile;\n 'defer-update': {\n logs: string[];\n progress: number;\n };\n notification: Notification;\n 'input-asked': ConfigurationBuilder;\n 'task-update': {\n id: string;\n progress: number;\n logs: string[];\n finished: boolean;\n failed: string | undefined;\n };\n 'get-app-details': {\n appID: number;\n storefront: string;\n };\n flag: {\n flag: string;\n value: string | string[];\n };\n}\n\nexport type BasicLibraryInfo = {\n name: string;\n capsuleImage: string;\n appID: number;\n storefront: string;\n};\n\nexport interface EventListenerTypes {\n /**\n * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.\n * @param socket\n * @returns\n */\n connect: (socket: ws) => void;\n\n /**\n * 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.\n * @param reason\n * @returns\n */\n disconnect: (reason: string) => void;\n /**\n * 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)\n * @param config\n * @returns\n */\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n /**\n * This event is called when the client provides a response to any event. This should be treated as middleware.\n * @param response\n * @returns\n */\n response: (response: any) => void;\n\n /**\n * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.\n * @param config\n * @returns\n */\n authenticate: (config: any) => void;\n /**\n * 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)\n * @param query\n * @param event\n * @returns\n */\n search: (\n query: { storefront: string; appID: number },\n event: EventResponse<SearchResult[]>\n ) => void;\n /**\n * 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)\n * @param data\n * @param event\n * @returns\n */\n setup: (\n data: {\n path: string;\n type: 'direct' | 'torrent' | 'magnet';\n name: string;\n usedRealDebrid: boolean;\n multiPartFiles?: {\n name: string;\n downloadURL: string;\n }[];\n appID: number;\n storefront: string;\n },\n event: EventResponse<\n Omit<\n LibraryInfo,\n | 'capsuleImage'\n | 'coverImage'\n | 'name'\n | 'appID'\n | 'storefront'\n | 'addonsource'\n | 'titleImage'\n >\n >\n ) => void;\n\n /**\n * This event is emitted when the client requires for a search to be performed. Input is the search query.\n * @param query\n * @param event\n * @returns\n */\n 'library-search': (\n query: string,\n event: EventResponse<BasicLibraryInfo[]>\n ) => void;\n\n /**\n * 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.\n * @param appID\n * @param event\n * @returns\n */\n 'game-details': (\n details: { appID: number; storefront: string },\n event: EventResponse<StoreData | undefined>\n ) => void;\n\n /**\n * 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)`.\n * @returns\n */\n exit: () => void;\n\n /**\n * 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.\n * @param appID\n * @param info\n * @param event\n * @returns\n */\n 'request-dl': (\n appID: number,\n info: SearchResult,\n event: EventResponse<SearchResult>\n ) => void;\n\n /**\n * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.\n * @param event\n * @returns\n */\n catalog: (\n event: Omit<\n EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>,\n 'askForInput'\n >\n ) => void;\n}\n\nexport interface StoreData {\n name: string;\n publishers: string[];\n developers: string[];\n appID: number;\n releaseDate: string;\n capsuleImage: string;\n coverImage: string;\n basicDescription: string;\n description: string;\n headerImage: string;\n}\nexport interface WebsocketMessageClient {\n event: OGIAddonClientSentEvent;\n id?: string;\n args: any;\n}\nexport interface WebsocketMessageServer {\n event: OGIAddonServerSentEvent;\n id?: string;\n args: any;\n}\n\n/**\n * The configuration for the addon. This is used to identify the addon and provide information about it.\n * Storefronts is an array of names of stores that the addon supports.\n */\nexport interface OGIAddonConfiguration {\n name: string;\n id: string;\n description: string;\n version: string;\n\n author: string;\n repository: string;\n storefronts: string[];\n}\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonEvent[] = [];\n private registeredConnectEvent: boolean = false;\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonEvent}\n * @param listener {EventListenerTypes[OGIAddonEvent]}\n */\n public on<T extends OGIAddonEvent>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonEvent>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: Notification) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n const id = this.addonWSListener.send('get-app-details', {\n appID,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n StoreData | undefined\n >(id);\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @param id {string}\n * @param progress {number}\n * @param logs {string[]}\n */\n public async task() {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new CustomTask(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n}\n\nexport class CustomTask {\n public readonly id: string;\n public progress: number;\n public logs: string[];\n public finished: boolean = false;\n public ws: OGIAddonWSListener;\n public failed: string | undefined = undefined;\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n ) {\n this.id = id;\n this.progress = progress;\n this.logs = logs;\n this.ws = ws;\n }\n public log(log: string) {\n this.logs.push(log);\n this.update();\n }\n public finish() {\n this.finished = true;\n this.update();\n }\n public fail(message: string) {\n this.failed = message;\n this.update();\n }\n public setProgress(progress: number) {\n this.progress = progress;\n this.update();\n }\n public update() {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport interface LibraryInfo {\n name: string;\n version: string;\n cwd: string;\n appID: number;\n launchExecutable: string;\n launchArguments?: string;\n capsuleImage: string;\n storefront: string;\n addonsource: string;\n coverImage: string;\n titleImage?: string;\n}\ninterface Notification {\n type: 'warning' | 'error' | 'info' | 'success';\n message: string;\n id: string;\n}\nclass OGIAddonWSListener {\n private socket: WebSocket;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n if (\n process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'\n ) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n this.socket = new ws('ws://localhost:' + defaultPort);\n this.socket.on('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate', {\n ...this.addon.addonInfo,\n secret: process.argv[process.argv.length - 1].split('=')[1],\n ogiVersion: VERSION,\n });\n\n this.eventEmitter.emit('connect');\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n });\n\n this.socket.on('error', (error) => {\n if (error.message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', error);\n });\n\n this.socket.on('close', (code, reason) => {\n if (code === 1008) {\n console.error('Authentication failed:', reason);\n return;\n }\n this.eventEmitter.emit('disconnect', reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(reason.toString());\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked(\n configBuilt: ConfigurationBuilder,\n name: string,\n description: string,\n socket: WebSocket\n ): Promise<{ [key: string]: number | boolean | string }> {\n const config = configBuilt.build(false);\n const id = Math.random().toString(36).substring(7);\n if (!socket) {\n return {};\n }\n socket.send(\n JSON.stringify({\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n id: id,\n })\n );\n return await this.waitForResponseFromServer(id);\n }\n\n private registerMessageReceiver() {\n this.socket.on('message', async (data: string) => {\n const message: WebsocketMessageServer = JSON.parse(data);\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(message.args);\n if (!result[0]) {\n this.respondToMessage(\n message.id!!,\n {\n success: false,\n error: result[1],\n },\n undefined\n );\n } else {\n this.respondToMessage(message.id!!, { success: true }, undefined);\n }\n break;\n case 'search':\n let searchResultEvent = new EventResponse<SearchResult[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit('search', message.args, searchResultEvent);\n const searchResult =\n await this.waitForEventToRespond(searchResultEvent);\n this.respondToMessage(\n message.id!!,\n searchResult.data,\n searchResultEvent\n );\n break;\n case 'setup':\n let setupEvent = new EventResponse<LibraryInfo>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit(\n 'setup',\n {\n path: message.args.path,\n appID: message.args.appID,\n storefront: message.args.storefront,\n type: message.args.type,\n name: message.args.name,\n usedRealDebrid: message.args.usedRealDebrid,\n multiPartFiles: message.args.multiPartFiles,\n },\n setupEvent\n );\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID: message.args.deferID,\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data, setupEvent);\n break;\n case 'library-search':\n let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, [], librarySearchEvent);\n break;\n }\n this.eventEmitter.emit(\n 'library-search',\n message.args,\n librarySearchEvent\n );\n const librarySearchResult =\n await this.waitForEventToRespond(librarySearchEvent);\n this.respondToMessage(\n message.id!!,\n librarySearchResult.data,\n librarySearchEvent\n );\n break;\n case 'game-details':\n let gameDetailsEvent = new EventResponse<StoreData | undefined>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for game-details',\n },\n gameDetailsEvent\n );\n break;\n }\n this.eventEmitter.emit(\n 'game-details',\n message.args,\n gameDetailsEvent\n );\n const gameDetailsResult =\n await this.waitForEventToRespond(gameDetailsEvent);\n this.respondToMessage(\n message.id!!,\n gameDetailsResult.data,\n gameDetailsEvent\n );\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for request-dl',\n },\n requestDLEvent\n );\n break;\n }\n this.eventEmitter.emit(\n 'request-dl',\n message.args.appID,\n message.args.info,\n requestDLEvent\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, undefined, requestDLEvent);\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(\n message.id!!,\n requestDLResult.data,\n requestDLEvent\n );\n break;\n case 'catalog':\n let catalogEvent = new EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>();\n this.eventEmitter.emit('catalog', catalogEvent);\n const catalogResult = await this.waitForEventToRespond(catalogEvent);\n this.respondToMessage(message.id!!, catalogResult.data, catalogEvent);\n break;\n }\n });\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n public respondToMessage(\n messageID: string,\n response: any,\n originalEvent: EventResponse<any> | undefined\n ) {\n this.socket.send(\n JSON.stringify({\n event: 'response',\n id: messageID,\n args: response,\n statusError: originalEvent ? originalEvent.failed : undefined,\n })\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public waitForResponseFromServer<T>(messageID: string): Promise<T> {\n return new Promise((resolve) => {\n const waiter = (data: string) => {\n const message: WebsocketMessageClient = JSON.parse(data);\n if (message.event !== 'response') {\n this.socket.once('message', waiter);\n return;\n }\n console.log('received response from ' + messageID);\n\n if (message.id === messageID) {\n resolve(message.args);\n } else {\n this.socket.once('message', waiter);\n }\n };\n this.socket.once('message', waiter);\n });\n }\n\n public send(\n event: OGIAddonClientSentEvent,\n args: ClientSentEventTypes[OGIAddonClientSentEvent]\n ): string {\n // generate a random id\n const id = Math.random().toString(36).substring(7);\n this.socket.send(\n JSON.stringify({\n event,\n args,\n id,\n })\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n","import z, { ZodError } from 'zod';\n\nexport interface ConfigurationFile {\n [key: string]: ConfigurationOption;\n}\n\nconst configValidation = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n description: z.string().min(1),\n});\n\nexport function isStringOption(\n option: ConfigurationOption\n): option is StringOption {\n return option.type === 'string';\n}\n\nexport function isNumberOption(\n option: ConfigurationOption\n): option is NumberOption {\n return option.type === 'number';\n}\n\nexport function isBooleanOption(\n option: ConfigurationOption\n): option is BooleanOption {\n return option.type === 'boolean';\n}\n\nexport class ConfigurationBuilder {\n private options: ConfigurationOption[] = [];\n\n /**\n * Add a number option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: NumberOption) => NumberOption }\n * @returns\n */\n public addNumberOption(\n option: (option: NumberOption) => NumberOption\n ): ConfigurationBuilder {\n let newOption = new NumberOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a string option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: StringOption) => StringOption }\n */\n public addStringOption(option: (option: StringOption) => StringOption) {\n let newOption = new StringOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a boolean option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: BooleanOption) => BooleanOption }\n */\n public addBooleanOption(option: (option: BooleanOption) => BooleanOption) {\n let newOption = new BooleanOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n public build(includeFunctions: boolean): ConfigurationFile {\n let config: ConfigurationFile = {};\n this.options.forEach((option) => {\n // remove all functions from the option object\n if (!includeFunctions) {\n option = JSON.parse(JSON.stringify(option));\n const optionData = configValidation.safeParse(option);\n if (!optionData.success) {\n throw new ZodError(optionData.error.errors);\n }\n\n config[option.name] = option;\n } else {\n config[option.name] = option;\n }\n });\n return config;\n }\n}\n\nexport type ConfigurationOptionType = 'string' | 'number' | 'boolean' | 'unset';\nexport class ConfigurationOption {\n public name: string = '';\n public defaultValue: unknown = '';\n public displayName: string = '';\n public description: string = '';\n public type: ConfigurationOptionType = 'unset';\n\n /**\n * Set the name of the option. **REQUIRED**\n * @param name {string} The name of the option. This is used to reference the option in the configuration file.\n */\n setName(name: string) {\n this.name = name;\n return this;\n }\n\n /**\n * Set the display name of the option. This is used to show the user a human readable version of what the option is. **REQUIRED**\n * @param displayName {string} The display name of the option.\n * @returns\n */\n setDisplayName(displayName: string) {\n this.displayName = displayName;\n return this;\n }\n\n /**\n * Set the description of the option. This is to show the user a brief description of what this option does. **REQUIRED**\n * @param description {string} The description of the option.\n * @returns\n */\n setDescription(description: string) {\n this.description = description;\n return this;\n }\n\n /**\n * Validation code for the option. This is called when the user provides input to the option. If the validation fails, the user will be prompted to provide input again.\n * @param input {unknown} The input to validate\n */\n validate(input: unknown): [boolean, string] {\n throw new Error('Validation code not implemented. Value: ' + input);\n }\n}\n\nexport class StringOption extends ConfigurationOption {\n public allowedValues: string[] = [];\n public minTextLength: number = 0;\n public maxTextLength: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: string = '';\n public inputType: 'text' | 'file' | 'password' | 'folder' = 'text';\n public type: ConfigurationOptionType = 'string';\n\n /**\n * Set the allowed values for the string. If the array is empty, any value is allowed. When provided, the client will act like this option is a dropdown.\n * @param allowedValues {string[]} An array of allowed values for the string. If the array is empty, any value is allowed.\n */\n setAllowedValues(allowedValues: string[]): this {\n this.allowedValues = allowedValues;\n return this;\n }\n\n /**\n * Set the default value for the string. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {string} The default value for the string.\n */\n setDefaultValue(defaultValue: string): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n /**\n * Set the minimum text length for the string. If the user provides a string that is less than this value, the validation will fail.\n * @param minTextLength {number} The minimum text length for the string.\n */\n setMinTextLength(minTextLength: number): this {\n this.minTextLength = minTextLength;\n return this;\n }\n\n /**\n * Set the maximum text length for the string. If the user provides a string that is greater than this value, the validation will fail.\n * @param maxTextLength {number} The maximum text length for the string.\n */\n setMaxTextLength(maxTextLength: number): this {\n this.maxTextLength = maxTextLength;\n return this;\n }\n\n /**\n * Set the input type for the string. This will change how the client renders the input.\n * @param inputType {'text' | 'file' | 'password' | 'folder'} The input type for the string.\n */\n setInputType(inputType: 'text' | 'file' | 'password' | 'folder'): this {\n this.inputType = inputType;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'string') {\n return [false, 'Input is not a string'];\n }\n if (this.allowedValues.length === 0 && input.length !== 0)\n return [true, ''];\n if (\n input.length < this.minTextLength ||\n input.length > this.maxTextLength\n ) {\n return [\n false,\n 'Input is not within the text length ' +\n this.minTextLength +\n ' and ' +\n this.maxTextLength +\n ' characters (currently ' +\n input.length +\n ' characters)',\n ];\n }\n\n return [\n this.allowedValues.includes(input),\n 'Input is not an allowed value',\n ];\n }\n}\n\nexport class NumberOption extends ConfigurationOption {\n public min: number = 0;\n public max: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: number = 0;\n public type: ConfigurationOptionType = 'number';\n public inputType: 'range' | 'number' = 'number';\n\n /**\n * Set the minimum value for the number. If the user provides a number that is less than this value, the validation will fail.\n * @param min {number} The minimum value for the number.\n */\n setMin(min: number): this {\n this.min = min;\n return this;\n }\n\n /**\n * Set the input type for the number. This will change how the client renders the input.\n * @param type {'range' | 'number'} The input type for the number.\n */\n setInputType(type: 'range' | 'number'): this {\n this.inputType = type;\n return this;\n }\n\n /**\n * Set the maximum value for the number. If the user provides a number that is greater than this value, the validation will fail.\n * @param max {number} The maximum value for the number.\n */\n setMax(max: number): this {\n this.max = max;\n return this;\n }\n\n /**\n * Set the default value for the number. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {number} The default value for the number.\n */\n setDefaultValue(defaultValue: number): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (isNaN(Number(input))) {\n return [false, 'Input is not a number'];\n }\n if (Number(input) < this.min || Number(input) > this.max) {\n return [\n false,\n 'Input is not within the range of ' + this.min + ' and ' + this.max,\n ];\n }\n return [true, ''];\n }\n}\n\nexport class BooleanOption extends ConfigurationOption {\n public type: ConfigurationOptionType = 'boolean';\n public defaultValue: boolean = false;\n\n /**\n * Set the default value for the boolean. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {boolean} The default value for the boolean.\n */\n setDefaultValue(defaultValue: boolean): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'boolean') {\n return [false, 'Input is not a boolean'];\n }\n return [true, ''];\n }\n}\n","import {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n} from './ConfigurationBuilder';\n\ninterface DefiniteConfig {\n [key: string]: string | number | boolean;\n}\nexport class Configuration {\n readonly storedConfigTemplate: ConfigurationFile;\n definiteConfig: DefiniteConfig = {};\n constructor(configTemplate: ConfigurationFile) {\n this.storedConfigTemplate = configTemplate;\n }\n\n updateConfig(\n config: DefiniteConfig,\n validate: boolean = true\n ): [boolean, { [key: string]: string }] {\n this.definiteConfig = config;\n if (validate) {\n const result = this.validateConfig();\n return result;\n }\n return [true, {}];\n }\n // provides falsey or truthy value, and an error message if falsey\n private validateConfig(): [boolean, { [key: string]: string }] {\n const erroredKeys = new Map<string, string>();\n for (const key in this.storedConfigTemplate) {\n if (\n this.definiteConfig[key] === null ||\n this.definiteConfig[key] === undefined\n ) {\n console.warn(\n 'Option ' +\n key +\n ' is not defined. Using default value Value: ' +\n this.definiteConfig[key]\n );\n this.definiteConfig[key] = this.storedConfigTemplate[key]\n .defaultValue as string | number | boolean;\n }\n if (\n this.storedConfigTemplate[key].type !== typeof this.definiteConfig[key]\n ) {\n throw new Error('Option ' + key + ' is not of the correct type');\n }\n\n const result = this.storedConfigTemplate[key].validate(\n this.definiteConfig[key]\n );\n if (!result[0]) {\n erroredKeys.set(key, result[1]);\n }\n }\n\n for (const key in this.definiteConfig) {\n if (!this.storedConfigTemplate[key]) {\n throw new Error(\n 'Option ' + key + ' is not defined in the configuration template'\n );\n }\n }\n\n if (erroredKeys.size > 0) {\n return [false, Object.fromEntries(erroredKeys)];\n }\n\n return [true, Object.fromEntries(erroredKeys)];\n }\n\n getStringValue(optionName: string): string {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'string') {\n throw new Error('Option ' + optionName + ' is not a string');\n }\n return this.definiteConfig[optionName];\n }\n\n getNumberValue(optionName: string): number {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'number') {\n throw new Error('Option ' + optionName + ' is not a number');\n }\n return this.definiteConfig[optionName];\n }\n\n getBooleanValue(optionName: string): boolean {\n if (this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'boolean') {\n throw new Error('Option ' + optionName + ' is not a boolean');\n }\n return this.definiteConfig[optionName];\n }\n}\n\nexport {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n};\n","import { ConfigurationBuilder } from './main';\n\nexport default class EventResponse<T> {\n data: T | undefined = undefined;\n deffered: boolean = false;\n resolved: boolean = false;\n progress: number = 0;\n logs: string[] = [];\n failed: string | undefined = undefined;\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>;\n\n constructor(\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>\n ) {\n this.onInputAsked = onInputAsked;\n }\n\n public defer(promise?: () => Promise<void>) {\n this.deffered = true;\n // include this to make it easier to use the defer method with async functions\n if (promise) {\n promise();\n }\n }\n\n /**\n * Resolve the event with data. This acts like a promise resolve, and will stop the event from being processed further. **You must always call this method when you are done with the event.**\n * @param data {T}\n */\n public resolve(data: T) {\n this.resolved = true;\n this.data = data;\n }\n\n /**\n * Completes the event and resolves it, but does not return any data. **You must always call this method when you are done with the event.**\n */\n public complete() {\n this.resolved = true;\n }\n\n public fail(message: string) {\n this.resolved = true;\n this.failed = message;\n }\n\n /**\n * Logs a message to the event. This is useful for debugging and logging information to the user.\n * @param message {string}\n */\n public log(message: string) {\n this.logs.push(message);\n }\n\n /**\n * Send a screen to the client to ask for input. Use the `ConfigurationBuilder` system to build the screen. Once sent to the user, the addon cannot change the screen.\n * @async\n * @param name {string}\n * @param description {string}\n * @param screen {ConfigurationBuilder}\n * @returns {Promise<{ [key: string]: boolean | string | number }>}\n */\n public async askForInput(\n name: string,\n description: string,\n screen: ConfigurationBuilder\n ): Promise<{ [key: string]: boolean | string | number }> {\n if (!this.onInputAsked) {\n throw new Error('No input asked callback');\n }\n return await this.onInputAsked(screen, name, description);\n }\n}\n","{\n \"name\": \"ogi-addon\",\n \"module\": \"./build/main.js\",\n \"type\": \"module\",\n \"main\": \"./build/main.cjs\",\n \"version\": \"1.5.2\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"default\": \"./build/main.js\",\n \"types\": \"./build/main.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/main.cjs\",\n \"types\": \"./build/main.d.cts\"\n }\n },\n \"./config\": {\n \"import\": {\n \"default\": \"./build/config/Configuration.js\",\n \"types\": \"./build/config/Configuration.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/config/Configuration.cjs\",\n \"types\": \"./build/config/Configuration.d.cts\"\n }\n }\n },\n \"typings\": \"./build/main.d.ts\",\n \"author\": {\n \"name\": \"Nat3z\",\n \"email\": \"me@nat3z.com\",\n \"url\": \"https://nat3z.com/\"\n },\n \"dependencies\": {\n \"fuse.js\": \"^7.1.0\",\n \"ws\": \"^8.4.0\",\n \"zod\": \"^3.23.8\"\n },\n \"scripts\": {\n \"auto-build\": \"tsc -w\",\n \"build\": \"tsup --config tsup.config.js\",\n \"release\": \"bun run build && npm publish\",\n \"release-beta\": \"bun run build && npm publish --tag future\"\n },\n \"devDependencies\": {\n \"@types/minimatch\": \"^6.0.0\",\n \"@types/node\": \"^20.14.12\",\n \"@types/ws\": \"^8.4.0\",\n \"prettier\": \"^3.6.0\",\n \"tsup\": \"^8.2.3\",\n \"typescript\": \"^5.0.0\"\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAA8B;AAC9B,yBAAmB;;;ACDnB,iBAA4B;AAM5B,IAAM,mBAAmB,WAAAA,QAAE,OAAO;AAAA,EAChC,MAAM,WAAAA,QAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,aAAa,WAAAA,QAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,aAAa,WAAAA,QAAE,OAAO,EAAE,IAAI,CAAC;AAC/B,CAAC;AAoBM,IAAM,uBAAN,MAA2B;AAAA,EACxB,UAAiC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnC,gBACL,QACsB;AACtB,QAAI,YAAY,IAAI,aAAa;AACjC,gBAAY,OAAO,SAAS;AAC5B,SAAK,QAAQ,KAAK,SAAS;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,gBAAgB,QAAgD;AACrE,QAAI,YAAY,IAAI,aAAa;AACjC,gBAAY,OAAO,SAAS;AAC5B,SAAK,QAAQ,KAAK,SAAS;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,iBAAiB,QAAkD;AACxE,QAAI,YAAY,IAAI,cAAc;AAClC,gBAAY,OAAO,SAAS;AAC5B,SAAK,QAAQ,KAAK,SAAS;AAC3B,WAAO;AAAA,EACT;AAAA,EAEO,MAAM,kBAA8C;AACzD,QAAI,SAA4B,CAAC;AACjC,SAAK,QAAQ,QAAQ,CAAC,WAAW;AAE/B,UAAI,CAAC,kBAAkB;AACrB,iBAAS,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;AAC1C,cAAM,aAAa,iBAAiB,UAAU,MAAM;AACpD,YAAI,CAAC,WAAW,SAAS;AACvB,gBAAM,IAAI,oBAAS,WAAW,MAAM,MAAM;AAAA,QAC5C;AAEA,eAAO,OAAO,IAAI,IAAI;AAAA,MACxB,OAAO;AACL,eAAO,OAAO,IAAI,IAAI;AAAA,MACxB;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAGO,IAAM,sBAAN,MAA0B;AAAA,EACxB,OAAe;AAAA,EACf,eAAwB;AAAA,EACxB,cAAsB;AAAA,EACtB,cAAsB;AAAA,EACtB,OAAgC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvC,QAAQ,MAAc;AACpB,SAAK,OAAO;AACZ,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,aAAqB;AAClC,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,aAAqB;AAClC,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAmC;AAC1C,UAAM,IAAI,MAAM,6CAA6C,KAAK;AAAA,EACpE;AACF;AAEO,IAAM,eAAN,cAA2B,oBAAoB;AAAA,EAC7C,gBAA0B,CAAC;AAAA,EAC3B,gBAAwB;AAAA,EACxB,gBAAwB,OAAO;AAAA,EAC/B,eAAuB;AAAA,EACvB,YAAqD;AAAA,EACrD,OAAgC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvC,iBAAiB,eAA+B;AAC9C,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,cAA4B;AAC1C,SAAK,eAAe;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,eAA6B;AAC5C,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,eAA6B;AAC5C,SAAK,gBAAgB;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAA0D;AACrE,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA,EAES,SAAS,OAAmC;AACnD,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,CAAC,OAAO,uBAAuB;AAAA,IACxC;AACA,QAAI,KAAK,cAAc,WAAW,KAAK,MAAM,WAAW;AACtD,aAAO,CAAC,MAAM,EAAE;AAClB,QACE,MAAM,SAAS,KAAK,iBACpB,MAAM,SAAS,KAAK,eACpB;AACA,aAAO;AAAA,QACL;AAAA,QACA,yCACE,KAAK,gBACL,UACA,KAAK,gBACL,4BACA,MAAM,SACN;AAAA,MACJ;AAAA,IACF;AAEA,WAAO;AAAA,MACL,KAAK,cAAc,SAAS,KAAK;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,eAAN,cAA2B,oBAAoB;AAAA,EAC7C,MAAc;AAAA,EACd,MAAc,OAAO;AAAA,EACrB,eAAuB;AAAA,EACvB,OAAgC;AAAA,EAChC,YAAgC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvC,OAAO,KAAmB;AACxB,SAAK,MAAM;AACX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,MAAgC;AAC3C,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAmB;AACxB,SAAK,MAAM;AACX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,cAA4B;AAC1C,SAAK,eAAe;AACpB,WAAO;AAAA,EACT;AAAA,EAES,SAAS,OAAmC;AACnD,QAAI,MAAM,OAAO,KAAK,CAAC,GAAG;AACxB,aAAO,CAAC,OAAO,uBAAuB;AAAA,IACxC;AACA,QAAI,OAAO,KAAK,IAAI,KAAK,OAAO,OAAO,KAAK,IAAI,KAAK,KAAK;AACxD,aAAO;AAAA,QACL;AAAA,QACA,sCAAsC,KAAK,MAAM,UAAU,KAAK;AAAA,MAClE;AAAA,IACF;AACA,WAAO,CAAC,MAAM,EAAE;AAAA,EAClB;AACF;AAEO,IAAM,gBAAN,cAA4B,oBAAoB;AAAA,EAC9C,OAAgC;AAAA,EAChC,eAAwB;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,gBAAgB,cAA6B;AAC3C,SAAK,eAAe;AACpB,WAAO;AAAA,EACT;AAAA,EAES,SAAS,OAAmC;AACnD,QAAI,OAAO,UAAU,WAAW;AAC9B,aAAO,CAAC,OAAO,wBAAwB;AAAA,IACzC;AACA,WAAO,CAAC,MAAM,EAAE;AAAA,EAClB;AACF;;;ACrRO,IAAM,gBAAN,MAAoB;AAAA,EAChB;AAAA,EACT,iBAAiC,CAAC;AAAA,EAClC,YAAY,gBAAmC;AAC7C,SAAK,uBAAuB;AAAA,EAC9B;AAAA,EAEA,aACE,QACA,WAAoB,MACkB;AACtC,SAAK,iBAAiB;AACtB,QAAI,UAAU;AACZ,YAAM,SAAS,KAAK,eAAe;AACnC,aAAO;AAAA,IACT;AACA,WAAO,CAAC,MAAM,CAAC,CAAC;AAAA,EAClB;AAAA;AAAA,EAEQ,iBAAuD;AAC7D,UAAM,cAAc,oBAAI,IAAoB;AAC5C,eAAW,OAAO,KAAK,sBAAsB;AAC3C,UACE,KAAK,eAAe,GAAG,MAAM,QAC7B,KAAK,eAAe,GAAG,MAAM,QAC7B;AACA,gBAAQ;AAAA,UACN,YACE,MACA,iDACA,KAAK,eAAe,GAAG;AAAA,QAC3B;AACA,aAAK,eAAe,GAAG,IAAI,KAAK,qBAAqB,GAAG,EACrD;AAAA,MACL;AACA,UACE,KAAK,qBAAqB,GAAG,EAAE,SAAS,OAAO,KAAK,eAAe,GAAG,GACtE;AACA,cAAM,IAAI,MAAM,YAAY,MAAM,6BAA6B;AAAA,MACjE;AAEA,YAAM,SAAS,KAAK,qBAAqB,GAAG,EAAE;AAAA,QAC5C,KAAK,eAAe,GAAG;AAAA,MACzB;AACA,UAAI,CAAC,OAAO,CAAC,GAAG;AACd,oBAAY,IAAI,KAAK,OAAO,CAAC,CAAC;AAAA,MAChC;AAAA,IACF;AAEA,eAAW,OAAO,KAAK,gBAAgB;AACrC,UAAI,CAAC,KAAK,qBAAqB,GAAG,GAAG;AACnC,cAAM,IAAI;AAAA,UACR,YAAY,MAAM;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,YAAY,OAAO,GAAG;AACxB,aAAO,CAAC,OAAO,OAAO,YAAY,WAAW,CAAC;AAAA,IAChD;AAEA,WAAO,CAAC,MAAM,OAAO,YAAY,WAAW,CAAC;AAAA,EAC/C;AAAA,EAEA,eAAe,YAA4B;AACzC,QAAI,CAAC,KAAK,eAAe,UAAU,MAAM,MAAM;AAC7C,YAAM,IAAI,MAAM,YAAY,aAAa,iBAAiB;AAAA,IAC5D;AACA,QAAI,OAAO,KAAK,eAAe,UAAU,MAAM,UAAU;AACvD,YAAM,IAAI,MAAM,YAAY,aAAa,kBAAkB;AAAA,IAC7D;AACA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA,EAEA,eAAe,YAA4B;AACzC,QAAI,CAAC,KAAK,eAAe,UAAU,MAAM,MAAM;AAC7C,YAAM,IAAI,MAAM,YAAY,aAAa,iBAAiB;AAAA,IAC5D;AACA,QAAI,OAAO,KAAK,eAAe,UAAU,MAAM,UAAU;AACvD,YAAM,IAAI,MAAM,YAAY,aAAa,kBAAkB;AAAA,IAC7D;AACA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA,EAEA,gBAAgB,YAA6B;AAC3C,QAAI,KAAK,eAAe,UAAU,MAAM,MAAM;AAC5C,YAAM,IAAI,MAAM,YAAY,aAAa,iBAAiB;AAAA,IAC5D;AACA,QAAI,OAAO,KAAK,eAAe,UAAU,MAAM,WAAW;AACxD,YAAM,IAAI,MAAM,YAAY,aAAa,mBAAmB;AAAA,IAC9D;AACA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AACF;;;AC3GA,IAAqB,gBAArB,MAAsC;AAAA,EACpC,OAAsB;AAAA,EACtB,WAAoB;AAAA,EACpB,WAAoB;AAAA,EACpB,WAAmB;AAAA,EACnB,OAAiB,CAAC;AAAA,EAClB,SAA6B;AAAA,EAC7B;AAAA,EAMA,YACE,cAKA;AACA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEO,MAAM,SAA+B;AAC1C,SAAK,WAAW;AAEhB,QAAI,SAAS;AACX,cAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,QAAQ,MAAS;AACtB,SAAK,WAAW;AAChB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKO,WAAW;AAChB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEO,KAAK,SAAiB;AAC3B,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,IAAI,SAAiB;AAC1B,SAAK,KAAK,KAAK,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,YACX,MACA,aACA,QACuD;AACvD,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,WAAO,MAAM,KAAK,aAAa,QAAQ,MAAM,WAAW;AAAA,EAC1D;AACF;;;AHvEA,kBAAmC;;;AITnC;AAAA,EACE,MAAQ;AAAA,EACR,QAAU;AAAA,EACV,MAAQ;AAAA,EACR,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,SAAW;AAAA,IACT,KAAK;AAAA,MACH,QAAU;AAAA,QACR,SAAW;AAAA,QACX,OAAS;AAAA,MACX;AAAA,MACA,SAAW;AAAA,QACT,SAAW;AAAA,QACX,OAAS;AAAA,MACX;AAAA,IACF;AAAA,IACA,YAAY;AAAA,MACV,QAAU;AAAA,QACR,SAAW;AAAA,QACX,OAAS;AAAA,MACX;AAAA,MACA,SAAW;AAAA,QACT,SAAW;AAAA,QACX,OAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAW;AAAA,EACX,QAAU;AAAA,IACR,MAAQ;AAAA,IACR,OAAS;AAAA,IACT,KAAO;AAAA,EACT;AAAA,EACA,cAAgB;AAAA,IACd,WAAW;AAAA,IACX,IAAM;AAAA,IACN,KAAO;AAAA,EACT;AAAA,EACA,SAAW;AAAA,IACT,cAAc;AAAA,IACd,OAAS;AAAA,IACT,SAAW;AAAA,IACX,gBAAgB;AAAA,EAClB;AAAA,EACA,iBAAmB;AAAA,IACjB,oBAAoB;AAAA,IACpB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,UAAY;AAAA,IACZ,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AACF;;;AJNA,IAAM,cAAc;AAEb,IAAM,UAAU,gBAAM;AAuO7B,IAAqB,WAArB,MAA8B;AAAA,EACrB,eAAe,IAAI,mBAAAC,QAAO,aAAa;AAAA,EACvC;AAAA,EACA;AAAA,EACA,SAAwB,IAAI,cAAc,CAAC,CAAC;AAAA,EAC3C,kBAAmC,CAAC;AAAA,EACpC,yBAAkC;AAAA,EAE1C,YAAY,WAAkC;AAC5C,SAAK,YAAY;AACjB,SAAK,kBAAkB,IAAI,mBAAmB,MAAM,KAAK,YAAY;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,GACL,OACA,UACA;AACA,SAAK,aAAa,GAAG,OAAO,QAAQ;AACpC,SAAK,gBAAgB,KAAK,KAAK;AAE/B,QAAI,CAAC,KAAK,wBAAwB;AAChC,WAAK,gBAAgB,aAAa,KAAK,WAAW,MAAM;AACtD,aAAK,gBAAgB,KAAK,QAAQ;AAAA,UAChC,MAAM;AAAA,UACN,OAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AACD,WAAK,yBAAyB;AAAA,IAChC;AAAA,EACF;AAAA,EAEO,KACL,UACG,MACH;AACA,SAAK,aAAa,KAAK,OAAO,GAAG,IAAI;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,OAAO,cAA4B;AACxC,SAAK,gBAAgB,KAAK,gBAAgB,CAAC,YAAY,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,cAAc,OAAe,YAAoB;AAC5D,UAAM,KAAK,KAAK,gBAAgB,KAAK,mBAAmB;AAAA,MACtD;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,MAAM,KAAK,gBAAgB,0BAEhC,EAAE;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,OAAO;AAClB,UAAM,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC;AACjD,UAAM,WAAW;AACjB,UAAM,OAAiB,CAAC;AACxB,UAAM,OAAO,IAAI,WAAW,KAAK,iBAAiB,IAAI,UAAU,IAAI;AACpE,SAAK,gBAAgB,KAAK,eAAe;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAEO,IAAM,aAAN,MAAiB;AAAA,EACN;AAAA,EACT;AAAA,EACA;AAAA,EACA,WAAoB;AAAA,EACpB;AAAA,EACA,SAA6B;AAAA,EACpC,YACEC,KACA,IACA,UACA,MACA;AACA,SAAK,KAAK;AACV,SAAK,WAAW;AAChB,SAAK,OAAO;AACZ,SAAK,KAAKA;AAAA,EACZ;AAAA,EACO,IAAI,KAAa;AACtB,SAAK,KAAK,KAAK,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AAAA,EACO,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,OAAO;AAAA,EACd;AAAA,EACO,KAAK,SAAiB;AAC3B,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AAAA,EACO,YAAY,UAAkB;AACnC,SAAK,WAAW;AAChB,SAAK,OAAO;AAAA,EACd;AAAA,EACO,SAAS;AACd,SAAK,GAAG,KAAK,eAAe;AAAA,MAC1B,IAAI,KAAK;AAAA,MACT,UAAU,KAAK;AAAA,MACf,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AACF;AASO,IAAM,aAAN,MAAoB;AAAA,EACjB;AAAA,EACR,YACE,OACA,MACA,UAAyC;AAAA,IACvC,WAAW;AAAA,IACX,cAAc;AAAA,EAChB,GACA;AACA,SAAK,OAAO,IAAI,YAAAC,QAAK,OAAO;AAAA,MAC1B;AAAA,MACA,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA,EACO,OAAO,OAAe,QAAgB,IAAS;AACpD,WAAO,KAAK,KACT,OAAO,KAAK,EACZ,MAAM,GAAG,KAAK,EACd,IAAI,CAAC,WAAW,OAAO,IAAI;AAAA,EAChC;AAAA,EACO,SAAS,OAAY;AAC1B,UAAM,IAAI,CAAC,SAAS,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,EACzC;AACF;AAsBA,IAAM,qBAAN,MAAyB;AAAA,EACf;AAAA,EACD;AAAA,EACA;AAAA,EAEP,YAAY,UAAoB,cAAmC;AACjE,QACE,QAAQ,KAAK,QAAQ,KAAK,SAAS,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,iBACxD;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,QAAQ;AACb,SAAK,eAAe;AACpB,SAAK,SAAS,IAAI,UAAAD,QAAG,oBAAoB,WAAW;AACpD,SAAK,OAAO,GAAG,QAAQ,MAAM;AAC3B,cAAQ,IAAI,+BAA+B;AAC3C,cAAQ,IAAI,6BAA6B,OAAO;AAGhD,WAAK,KAAK,gBAAgB;AAAA,QACxB,GAAG,KAAK,MAAM;AAAA,QACd,QAAQ,QAAQ,KAAK,QAAQ,KAAK,SAAS,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,QAC1D,YAAY;AAAA,MACd,CAAC;AAED,WAAK,aAAa,KAAK,SAAS;AAGhC,UAAI,gBAAgB,IAAI,qBAAqB;AAC7C,WAAK,aAAa,KAAK,aAAa,aAAa;AACjD,WAAK,KAAK,aAAa,cAAc,MAAM,KAAK,CAAC;AACjD,WAAK,MAAM,SAAS,IAAI,cAAc,cAAc,MAAM,IAAI,CAAC;AAAA,IACjE,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,UAAU;AACjC,UAAI,MAAM,QAAQ,SAAS,mBAAmB,GAAG;AAC/C,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,cAAQ,MAAM,sBAAsB,KAAK;AAAA,IAC3C,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,MAAM,WAAW;AACxC,UAAI,SAAS,MAAM;AACjB,gBAAQ,MAAM,0BAA0B,MAAM;AAC9C;AAAA,MACF;AACA,WAAK,aAAa,KAAK,cAAc,MAAM;AAC3C,cAAQ,IAAI,oCAAoC;AAChD,cAAQ,MAAM,OAAO,SAAS,CAAC;AAC/B,WAAK,aAAa,KAAK,MAAM;AAC7B,WAAK,OAAO,MAAM;AAAA,IACpB,CAAC;AAED,SAAK,wBAAwB;AAAA,EAC/B;AAAA,EAEA,MAAc,eACZ,aACA,MACA,aACA,QACuD;AACvD,UAAM,SAAS,YAAY,MAAM,KAAK;AACtC,UAAM,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC;AACjD,QAAI,CAAC,QAAQ;AACX,aAAO,CAAC;AAAA,IACV;AACA,WAAO;AAAA,MACL,KAAK,UAAU;AAAA,QACb,OAAO;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO,MAAM,KAAK,0BAA0B,EAAE;AAAA,EAChD;AAAA,EAEQ,0BAA0B;AAChC,SAAK,OAAO,GAAG,WAAW,OAAO,SAAiB;AAChD,YAAM,UAAkC,KAAK,MAAM,IAAI;AACvD,cAAQ,QAAQ,OAAO;AAAA,QACrB,KAAK;AACH,gBAAM,SAAS,KAAK,MAAM,OAAO,aAAa,QAAQ,IAAI;AAC1D,cAAI,CAAC,OAAO,CAAC,GAAG;AACd,iBAAK;AAAA,cACH,QAAQ;AAAA,cACR;AAAA,gBACE,SAAS;AAAA,gBACT,OAAO,OAAO,CAAC;AAAA,cACjB;AAAA,cACA;AAAA,YACF;AAAA,UACF,OAAO;AACL,iBAAK,iBAAiB,QAAQ,IAAM,EAAE,SAAS,KAAK,GAAG,MAAS;AAAA,UAClE;AACA;AAAA,QACF,KAAK;AACH,cAAI,oBAAoB,IAAI;AAAA,YAC1B,CAAC,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,aAAa,KAAK,MAAM;AAAA,UAC9D;AACA,eAAK,aAAa,KAAK,UAAU,QAAQ,MAAM,iBAAiB;AAChE,gBAAM,eACJ,MAAM,KAAK,sBAAsB,iBAAiB;AACpD,eAAK;AAAA,YACH,QAAQ;AAAA,YACR,aAAa;AAAA,YACb;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,cAAI,aAAa,IAAI;AAAA,YACnB,CAAC,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,aAAa,KAAK,MAAM;AAAA,UAC9D;AACA,eAAK,aAAa;AAAA,YAChB;AAAA,YACA;AAAA,cACE,MAAM,QAAQ,KAAK;AAAA,cACnB,OAAO,QAAQ,KAAK;AAAA,cACpB,YAAY,QAAQ,KAAK;AAAA,cACzB,MAAM,QAAQ,KAAK;AAAA,cACnB,MAAM,QAAQ,KAAK;AAAA,cACnB,gBAAgB,QAAQ,KAAK;AAAA,cAC7B,gBAAgB,QAAQ,KAAK;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AACA,gBAAM,WAAW,YAAY,MAAM;AACjC,gBAAI,WAAW,UAAU;AACvB,4BAAc,QAAQ;AACtB;AAAA,YACF;AACA,iBAAK,KAAK,gBAAgB;AAAA,cACxB,MAAM,WAAW;AAAA,cACjB,SAAS,QAAQ,KAAK;AAAA,cACtB,UAAU,WAAW;AAAA,cACrB,QAAQ,WAAW;AAAA,YACrB,CAAyC;AAAA,UAC3C,GAAG,GAAG;AACN,gBAAM,cAAc,MAAM,KAAK,sBAAsB,UAAU;AAC/D,eAAK,iBAAiB,QAAQ,IAAM,YAAY,MAAM,UAAU;AAChE;AAAA,QACF,KAAK;AACH,cAAI,qBAAqB,IAAI;AAAA,YAC3B,CAAC,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,aAAa,KAAK,MAAM;AAAA,UAC9D;AACA,cAAI,KAAK,aAAa,cAAc,cAAc,MAAM,GAAG;AACzD,iBAAK,iBAAiB,QAAQ,IAAM,CAAC,GAAG,kBAAkB;AAC1D;AAAA,UACF;AACA,eAAK,aAAa;AAAA,YAChB;AAAA,YACA,QAAQ;AAAA,YACR;AAAA,UACF;AACA,gBAAM,sBACJ,MAAM,KAAK,sBAAsB,kBAAkB;AACrD,eAAK;AAAA,YACH,QAAQ;AAAA,YACR,oBAAoB;AAAA,YACpB;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,cAAI,mBAAmB,IAAI;AAAA,YACzB,CAAC,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,aAAa,KAAK,MAAM;AAAA,UAC9D;AACA,cAAI,KAAK,aAAa,cAAc,cAAc,MAAM,GAAG;AACzD,iBAAK;AAAA,cACH,QAAQ;AAAA,cACR;AAAA,gBACE,OAAO;AAAA,cACT;AAAA,cACA;AAAA,YACF;AACA;AAAA,UACF;AACA,eAAK,aAAa;AAAA,YAChB;AAAA,YACA,QAAQ;AAAA,YACR;AAAA,UACF;AACA,gBAAM,oBACJ,MAAM,KAAK,sBAAsB,gBAAgB;AACnD,eAAK;AAAA,YACH,QAAQ;AAAA,YACR,kBAAkB;AAAA,YAClB;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,cAAI,iBAAiB,IAAI;AAAA,YACvB,CAAC,QAAQ,MAAM,gBACb,KAAK,eAAe,QAAQ,MAAM,aAAa,KAAK,MAAM;AAAA,UAC9D;AACA,cAAI,KAAK,aAAa,cAAc,YAAY,MAAM,GAAG;AACvD,iBAAK;AAAA,cACH,QAAQ;AAAA,cACR;AAAA,gBACE,OAAO;AAAA,cACT;AAAA,cACA;AAAA,YACF;AACA;AAAA,UACF;AACA,eAAK,aAAa;AAAA,YAChB;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,QAAQ,KAAK;AAAA,YACb;AAAA,UACF;AACA,gBAAM,kBACJ,MAAM,KAAK,sBAAsB,cAAc;AACjD,cAAI,eAAe,QAAQ;AACzB,iBAAK,iBAAiB,QAAQ,IAAM,QAAW,cAAc;AAC7D;AAAA,UACF;AACA,cACE,eAAe,SAAS,UACxB,eAAe,MAAM,iBAAiB,WACtC;AACA,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AACA,eAAK;AAAA,YACH,QAAQ;AAAA,YACR,gBAAgB;AAAA,YAChB;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,cAAI,eAAe,IAAI,cAMpB;AACH,eAAK,aAAa,KAAK,WAAW,YAAY;AAC9C,gBAAM,gBAAgB,MAAM,KAAK,sBAAsB,YAAY;AACnE,eAAK,iBAAiB,QAAQ,IAAM,cAAc,MAAM,YAAY;AACpE;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,sBACN,OAC2B;AAE3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,YAAY,MAAM;AAChC,YAAI,MAAM,UAAU;AAClB,kBAAQ,KAAK;AACb,uBAAa,OAAO;AAAA,QACtB;AAAA,MACF,GAAG,CAAC;AAEJ,YAAM,UAAU,WAAW,MAAM;AAC/B,YAAI,MAAM,UAAU;AAClB,wBAAc,OAAO;AACrB,gBAAM,WAAW,YAAY,MAAM;AACjC,gBAAI,MAAM,UAAU;AAClB,4BAAc,QAAQ;AACtB,sBAAQ,KAAK;AAAA,YACf;AAAA,UACF,GAAG,GAAG;AAAA,QACR,OAAO;AACL,iBAAO,+BAA+B;AAAA,QACxC;AAAA,MACF,GAAG,GAAI;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEO,iBACL,WACA,UACA,eACA;AACA,SAAK,OAAO;AAAA,MACV,KAAK,UAAU;AAAA,QACb,OAAO;AAAA,QACP,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,aAAa,gBAAgB,cAAc,SAAS;AAAA,MACtD,CAAC;AAAA,IACH;AACA,YAAQ,IAAI,4BAA4B,SAAS;AAAA,EACnD;AAAA,EAEO,0BAA6B,WAA+B;AACjE,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,SAAS,CAAC,SAAiB;AAC/B,cAAM,UAAkC,KAAK,MAAM,IAAI;AACvD,YAAI,QAAQ,UAAU,YAAY;AAChC,eAAK,OAAO,KAAK,WAAW,MAAM;AAClC;AAAA,QACF;AACA,gBAAQ,IAAI,4BAA4B,SAAS;AAEjD,YAAI,QAAQ,OAAO,WAAW;AAC5B,kBAAQ,QAAQ,IAAI;AAAA,QACtB,OAAO;AACL,eAAK,OAAO,KAAK,WAAW,MAAM;AAAA,QACpC;AAAA,MACF;AACA,WAAK,OAAO,KAAK,WAAW,MAAM;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEO,KACL,OACA,MACQ;AAER,UAAM,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,CAAC;AACjD,SAAK,OAAO;AAAA,MACV,KAAK,UAAU;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ;AACb,SAAK,OAAO,MAAM;AAAA,EACpB;AACF;","names":["z","events","ws","Fuse"]}
|
package/build/main.d.cts
CHANGED
|
@@ -293,7 +293,7 @@ declare class OGIAddonWSListener {
|
|
|
293
293
|
private userInputAsked;
|
|
294
294
|
private registerMessageReceiver;
|
|
295
295
|
private waitForEventToRespond;
|
|
296
|
-
respondToMessage(messageID: string, response: any): void;
|
|
296
|
+
respondToMessage(messageID: string, response: any, originalEvent: EventResponse<any> | undefined): void;
|
|
297
297
|
waitForResponseFromServer<T>(messageID: string): Promise<T>;
|
|
298
298
|
send(event: OGIAddonClientSentEvent, args: ClientSentEventTypes[OGIAddonClientSentEvent]): string;
|
|
299
299
|
close(): void;
|
package/build/main.d.ts
CHANGED
|
@@ -293,7 +293,7 @@ declare class OGIAddonWSListener {
|
|
|
293
293
|
private userInputAsked;
|
|
294
294
|
private registerMessageReceiver;
|
|
295
295
|
private waitForEventToRespond;
|
|
296
|
-
respondToMessage(messageID: string, response: any): void;
|
|
296
|
+
respondToMessage(messageID: string, response: any, originalEvent: EventResponse<any> | undefined): void;
|
|
297
297
|
waitForResponseFromServer<T>(messageID: string): Promise<T>;
|
|
298
298
|
send(event: OGIAddonClientSentEvent, args: ClientSentEventTypes[OGIAddonClientSentEvent]): string;
|
|
299
299
|
close(): void;
|
package/build/main.js
CHANGED
|
@@ -378,7 +378,7 @@ var package_default = {
|
|
|
378
378
|
module: "./build/main.js",
|
|
379
379
|
type: "module",
|
|
380
380
|
main: "./build/main.cjs",
|
|
381
|
-
version: "1.5.
|
|
381
|
+
version: "1.5.2",
|
|
382
382
|
exports: {
|
|
383
383
|
".": {
|
|
384
384
|
import: {
|
|
@@ -635,12 +635,16 @@ var OGIAddonWSListener = class {
|
|
|
635
635
|
case "config-update":
|
|
636
636
|
const result = this.addon.config.updateConfig(message.args);
|
|
637
637
|
if (!result[0]) {
|
|
638
|
-
this.respondToMessage(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
638
|
+
this.respondToMessage(
|
|
639
|
+
message.id,
|
|
640
|
+
{
|
|
641
|
+
success: false,
|
|
642
|
+
error: result[1]
|
|
643
|
+
},
|
|
644
|
+
void 0
|
|
645
|
+
);
|
|
642
646
|
} else {
|
|
643
|
-
this.respondToMessage(message.id, { success: true });
|
|
647
|
+
this.respondToMessage(message.id, { success: true }, void 0);
|
|
644
648
|
}
|
|
645
649
|
break;
|
|
646
650
|
case "search":
|
|
@@ -649,7 +653,11 @@ var OGIAddonWSListener = class {
|
|
|
649
653
|
);
|
|
650
654
|
this.eventEmitter.emit("search", message.args, searchResultEvent);
|
|
651
655
|
const searchResult = await this.waitForEventToRespond(searchResultEvent);
|
|
652
|
-
this.respondToMessage(
|
|
656
|
+
this.respondToMessage(
|
|
657
|
+
message.id,
|
|
658
|
+
searchResult.data,
|
|
659
|
+
searchResultEvent
|
|
660
|
+
);
|
|
653
661
|
break;
|
|
654
662
|
case "setup":
|
|
655
663
|
let setupEvent = new EventResponse(
|
|
@@ -681,14 +689,14 @@ var OGIAddonWSListener = class {
|
|
|
681
689
|
});
|
|
682
690
|
}, 100);
|
|
683
691
|
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
684
|
-
this.respondToMessage(message.id, setupResult.data);
|
|
692
|
+
this.respondToMessage(message.id, setupResult.data, setupEvent);
|
|
685
693
|
break;
|
|
686
694
|
case "library-search":
|
|
687
695
|
let librarySearchEvent = new EventResponse(
|
|
688
696
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
689
697
|
);
|
|
690
698
|
if (this.eventEmitter.listenerCount("game-details") === 0) {
|
|
691
|
-
this.respondToMessage(message.id, []);
|
|
699
|
+
this.respondToMessage(message.id, [], librarySearchEvent);
|
|
692
700
|
break;
|
|
693
701
|
}
|
|
694
702
|
this.eventEmitter.emit(
|
|
@@ -697,16 +705,24 @@ var OGIAddonWSListener = class {
|
|
|
697
705
|
librarySearchEvent
|
|
698
706
|
);
|
|
699
707
|
const librarySearchResult = await this.waitForEventToRespond(librarySearchEvent);
|
|
700
|
-
this.respondToMessage(
|
|
708
|
+
this.respondToMessage(
|
|
709
|
+
message.id,
|
|
710
|
+
librarySearchResult.data,
|
|
711
|
+
librarySearchEvent
|
|
712
|
+
);
|
|
701
713
|
break;
|
|
702
714
|
case "game-details":
|
|
703
715
|
let gameDetailsEvent = new EventResponse(
|
|
704
716
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
705
717
|
);
|
|
706
718
|
if (this.eventEmitter.listenerCount("game-details") === 0) {
|
|
707
|
-
this.respondToMessage(
|
|
708
|
-
|
|
709
|
-
|
|
719
|
+
this.respondToMessage(
|
|
720
|
+
message.id,
|
|
721
|
+
{
|
|
722
|
+
error: "No event listener for game-details"
|
|
723
|
+
},
|
|
724
|
+
gameDetailsEvent
|
|
725
|
+
);
|
|
710
726
|
break;
|
|
711
727
|
}
|
|
712
728
|
this.eventEmitter.emit(
|
|
@@ -715,16 +731,24 @@ var OGIAddonWSListener = class {
|
|
|
715
731
|
gameDetailsEvent
|
|
716
732
|
);
|
|
717
733
|
const gameDetailsResult = await this.waitForEventToRespond(gameDetailsEvent);
|
|
718
|
-
this.respondToMessage(
|
|
734
|
+
this.respondToMessage(
|
|
735
|
+
message.id,
|
|
736
|
+
gameDetailsResult.data,
|
|
737
|
+
gameDetailsEvent
|
|
738
|
+
);
|
|
719
739
|
break;
|
|
720
740
|
case "request-dl":
|
|
721
741
|
let requestDLEvent = new EventResponse(
|
|
722
742
|
(screen, name, description) => this.userInputAsked(screen, name, description, this.socket)
|
|
723
743
|
);
|
|
724
744
|
if (this.eventEmitter.listenerCount("request-dl") === 0) {
|
|
725
|
-
this.respondToMessage(
|
|
726
|
-
|
|
727
|
-
|
|
745
|
+
this.respondToMessage(
|
|
746
|
+
message.id,
|
|
747
|
+
{
|
|
748
|
+
error: "No event listener for request-dl"
|
|
749
|
+
},
|
|
750
|
+
requestDLEvent
|
|
751
|
+
);
|
|
728
752
|
break;
|
|
729
753
|
}
|
|
730
754
|
this.eventEmitter.emit(
|
|
@@ -735,9 +759,7 @@ var OGIAddonWSListener = class {
|
|
|
735
759
|
);
|
|
736
760
|
const requestDLResult = await this.waitForEventToRespond(requestDLEvent);
|
|
737
761
|
if (requestDLEvent.failed) {
|
|
738
|
-
this.respondToMessage(message.id,
|
|
739
|
-
statusError: requestDLEvent.failed
|
|
740
|
-
});
|
|
762
|
+
this.respondToMessage(message.id, void 0, requestDLEvent);
|
|
741
763
|
break;
|
|
742
764
|
}
|
|
743
765
|
if (requestDLEvent.data === void 0 || requestDLEvent.data?.downloadType === "request") {
|
|
@@ -745,13 +767,17 @@ var OGIAddonWSListener = class {
|
|
|
745
767
|
"Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type."
|
|
746
768
|
);
|
|
747
769
|
}
|
|
748
|
-
this.respondToMessage(
|
|
770
|
+
this.respondToMessage(
|
|
771
|
+
message.id,
|
|
772
|
+
requestDLResult.data,
|
|
773
|
+
requestDLEvent
|
|
774
|
+
);
|
|
749
775
|
break;
|
|
750
776
|
case "catalog":
|
|
751
777
|
let catalogEvent = new EventResponse();
|
|
752
778
|
this.eventEmitter.emit("catalog", catalogEvent);
|
|
753
779
|
const catalogResult = await this.waitForEventToRespond(catalogEvent);
|
|
754
|
-
this.respondToMessage(message.id, catalogResult.data);
|
|
780
|
+
this.respondToMessage(message.id, catalogResult.data, catalogEvent);
|
|
755
781
|
break;
|
|
756
782
|
}
|
|
757
783
|
});
|
|
@@ -779,12 +805,13 @@ var OGIAddonWSListener = class {
|
|
|
779
805
|
}, 5e3);
|
|
780
806
|
});
|
|
781
807
|
}
|
|
782
|
-
respondToMessage(messageID, response) {
|
|
808
|
+
respondToMessage(messageID, response, originalEvent) {
|
|
783
809
|
this.socket.send(
|
|
784
810
|
JSON.stringify({
|
|
785
811
|
event: "response",
|
|
786
812
|
id: messageID,
|
|
787
|
-
args: response
|
|
813
|
+
args: response,
|
|
814
|
+
statusError: originalEvent ? originalEvent.failed : void 0
|
|
788
815
|
})
|
|
789
816
|
);
|
|
790
817
|
console.log("dispatched response to " + messageID);
|
package/build/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts","../src/config/ConfigurationBuilder.ts","../src/config/Configuration.ts","../src/EventResponse.ts","../package.json"],"sourcesContent":["import ws, { WebSocket } from 'ws';\nimport events from 'node:events';\nimport {\n ConfigurationBuilder,\n ConfigurationFile,\n} from './config/ConfigurationBuilder';\nimport { Configuration } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport { SearchResult } from './SearchEngine';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\nexport type OGIAddonEvent =\n | 'connect'\n | 'disconnect'\n | 'configure'\n | 'authenticate'\n | 'search'\n | 'setup'\n | 'library-search'\n | 'game-details'\n | 'exit'\n | 'request-dl'\n | 'catalog';\n\nexport type OGIAddonClientSentEvent =\n | 'response'\n | 'authenticate'\n | 'configure'\n | 'defer-update'\n | 'notification'\n | 'input-asked'\n | 'get-app-details'\n | 'flag'\n | 'task-update';\n\nexport type OGIAddonServerSentEvent =\n | 'authenticate'\n | 'configure'\n | 'config-update'\n | 'search'\n | 'setup'\n | 'response'\n | 'library-search'\n | 'game-details'\n | 'request-dl'\n | 'catalog';\nexport { ConfigurationBuilder, Configuration, EventResponse, SearchResult };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nexport const VERSION = pjson.version;\n\nexport interface ClientSentEventTypes {\n response: any;\n authenticate: {\n name: string;\n id: string;\n description: string;\n version: string;\n author: string;\n };\n configure: ConfigurationFile;\n 'defer-update': {\n logs: string[];\n progress: number;\n };\n notification: Notification;\n 'input-asked': ConfigurationBuilder;\n 'task-update': {\n id: string;\n progress: number;\n logs: string[];\n finished: boolean;\n failed: string | undefined;\n };\n 'get-app-details': {\n appID: number;\n storefront: string;\n };\n flag: {\n flag: string;\n value: string | string[];\n };\n}\n\nexport type BasicLibraryInfo = {\n name: string;\n capsuleImage: string;\n appID: number;\n storefront: string;\n};\n\nexport interface EventListenerTypes {\n /**\n * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.\n * @param socket\n * @returns\n */\n connect: (socket: ws) => void;\n\n /**\n * 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.\n * @param reason\n * @returns\n */\n disconnect: (reason: string) => void;\n /**\n * 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)\n * @param config\n * @returns\n */\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n /**\n * This event is called when the client provides a response to any event. This should be treated as middleware.\n * @param response\n * @returns\n */\n response: (response: any) => void;\n\n /**\n * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.\n * @param config\n * @returns\n */\n authenticate: (config: any) => void;\n /**\n * 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)\n * @param query\n * @param event\n * @returns\n */\n search: (\n query: { storefront: string; appID: number },\n event: EventResponse<SearchResult[]>\n ) => void;\n /**\n * 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)\n * @param data\n * @param event\n * @returns\n */\n setup: (\n data: {\n path: string;\n type: 'direct' | 'torrent' | 'magnet';\n name: string;\n usedRealDebrid: boolean;\n multiPartFiles?: {\n name: string;\n downloadURL: string;\n }[];\n appID: number;\n storefront: string;\n },\n event: EventResponse<\n Omit<\n LibraryInfo,\n | 'capsuleImage'\n | 'coverImage'\n | 'name'\n | 'appID'\n | 'storefront'\n | 'addonsource'\n | 'titleImage'\n >\n >\n ) => void;\n\n /**\n * This event is emitted when the client requires for a search to be performed. Input is the search query.\n * @param query\n * @param event\n * @returns\n */\n 'library-search': (\n query: string,\n event: EventResponse<BasicLibraryInfo[]>\n ) => void;\n\n /**\n * 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.\n * @param appID\n * @param event\n * @returns\n */\n 'game-details': (\n details: { appID: number; storefront: string },\n event: EventResponse<StoreData | undefined>\n ) => void;\n\n /**\n * 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)`.\n * @returns\n */\n exit: () => void;\n\n /**\n * 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.\n * @param appID\n * @param info\n * @param event\n * @returns\n */\n 'request-dl': (\n appID: number,\n info: SearchResult,\n event: EventResponse<SearchResult>\n ) => void;\n\n /**\n * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.\n * @param event\n * @returns\n */\n catalog: (\n event: Omit<\n EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>,\n 'askForInput'\n >\n ) => void;\n}\n\nexport interface StoreData {\n name: string;\n publishers: string[];\n developers: string[];\n appID: number;\n releaseDate: string;\n capsuleImage: string;\n coverImage: string;\n basicDescription: string;\n description: string;\n headerImage: string;\n}\nexport interface WebsocketMessageClient {\n event: OGIAddonClientSentEvent;\n id?: string;\n args: any;\n}\nexport interface WebsocketMessageServer {\n event: OGIAddonServerSentEvent;\n id?: string;\n args: any;\n}\n\n/**\n * The configuration for the addon. This is used to identify the addon and provide information about it.\n * Storefronts is an array of names of stores that the addon supports.\n */\nexport interface OGIAddonConfiguration {\n name: string;\n id: string;\n description: string;\n version: string;\n\n author: string;\n repository: string;\n storefronts: string[];\n}\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonEvent[] = [];\n private registeredConnectEvent: boolean = false;\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonEvent}\n * @param listener {EventListenerTypes[OGIAddonEvent]}\n */\n public on<T extends OGIAddonEvent>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonEvent>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: Notification) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n const id = this.addonWSListener.send('get-app-details', {\n appID,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n StoreData | undefined\n >(id);\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @param id {string}\n * @param progress {number}\n * @param logs {string[]}\n */\n public async task() {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new CustomTask(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n}\n\nexport class CustomTask {\n public readonly id: string;\n public progress: number;\n public logs: string[];\n public finished: boolean = false;\n public ws: OGIAddonWSListener;\n public failed: string | undefined = undefined;\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n ) {\n this.id = id;\n this.progress = progress;\n this.logs = logs;\n this.ws = ws;\n }\n public log(log: string) {\n this.logs.push(log);\n this.update();\n }\n public finish() {\n this.finished = true;\n this.update();\n }\n public fail(message: string) {\n this.failed = message;\n this.update();\n }\n public setProgress(progress: number) {\n this.progress = progress;\n this.update();\n }\n public update() {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport interface LibraryInfo {\n name: string;\n version: string;\n cwd: string;\n appID: number;\n launchExecutable: string;\n launchArguments?: string;\n capsuleImage: string;\n storefront: string;\n addonsource: string;\n coverImage: string;\n titleImage?: string;\n}\ninterface Notification {\n type: 'warning' | 'error' | 'info' | 'success';\n message: string;\n id: string;\n}\nclass OGIAddonWSListener {\n private socket: WebSocket;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n if (\n process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'\n ) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n this.socket = new ws('ws://localhost:' + defaultPort);\n this.socket.on('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate', {\n ...this.addon.addonInfo,\n secret: process.argv[process.argv.length - 1].split('=')[1],\n ogiVersion: VERSION,\n });\n\n this.eventEmitter.emit('connect');\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n });\n\n this.socket.on('error', (error) => {\n if (error.message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', error);\n });\n\n this.socket.on('close', (code, reason) => {\n if (code === 1008) {\n console.error('Authentication failed:', reason);\n return;\n }\n this.eventEmitter.emit('disconnect', reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(reason.toString());\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked(\n configBuilt: ConfigurationBuilder,\n name: string,\n description: string,\n socket: WebSocket\n ): Promise<{ [key: string]: number | boolean | string }> {\n const config = configBuilt.build(false);\n const id = Math.random().toString(36).substring(7);\n if (!socket) {\n return {};\n }\n socket.send(\n JSON.stringify({\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n id: id,\n })\n );\n return await this.waitForResponseFromServer(id);\n }\n\n private registerMessageReceiver() {\n this.socket.on('message', async (data: string) => {\n const message: WebsocketMessageServer = JSON.parse(data);\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(message.args);\n if (!result[0]) {\n this.respondToMessage(message.id!!, {\n success: false,\n error: result[1],\n });\n } else {\n this.respondToMessage(message.id!!, { success: true });\n }\n break;\n case 'search':\n let searchResultEvent = new EventResponse<SearchResult[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit('search', message.args, searchResultEvent);\n const searchResult =\n await this.waitForEventToRespond(searchResultEvent);\n this.respondToMessage(message.id!!, searchResult.data);\n break;\n case 'setup':\n let setupEvent = new EventResponse<LibraryInfo>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit(\n 'setup',\n {\n path: message.args.path,\n appID: message.args.appID,\n storefront: message.args.storefront,\n type: message.args.type,\n name: message.args.name,\n usedRealDebrid: message.args.usedRealDebrid,\n multiPartFiles: message.args.multiPartFiles,\n },\n setupEvent\n );\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID: message.args.deferID,\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data);\n break;\n case 'library-search':\n let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, []);\n break;\n }\n this.eventEmitter.emit(\n 'library-search',\n message.args,\n librarySearchEvent\n );\n const librarySearchResult =\n await this.waitForEventToRespond(librarySearchEvent);\n this.respondToMessage(message.id!!, librarySearchResult.data);\n break;\n case 'game-details':\n let gameDetailsEvent = new EventResponse<StoreData | undefined>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, {\n error: 'No event listener for game-details',\n });\n break;\n }\n this.eventEmitter.emit(\n 'game-details',\n message.args,\n gameDetailsEvent\n );\n const gameDetailsResult =\n await this.waitForEventToRespond(gameDetailsEvent);\n this.respondToMessage(message.id!!, gameDetailsResult.data);\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(message.id!!, {\n error: 'No event listener for request-dl',\n });\n break;\n }\n this.eventEmitter.emit(\n 'request-dl',\n message.args.appID,\n message.args.info,\n requestDLEvent\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, {\n statusError: requestDLEvent.failed,\n });\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(message.id!!, requestDLResult.data);\n break;\n case 'catalog':\n let catalogEvent = new EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>();\n this.eventEmitter.emit('catalog', catalogEvent);\n const catalogResult = await this.waitForEventToRespond(catalogEvent);\n this.respondToMessage(message.id!!, catalogResult.data);\n break;\n }\n });\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n public respondToMessage(messageID: string, response: any) {\n this.socket.send(\n JSON.stringify({\n event: 'response',\n id: messageID,\n args: response,\n })\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public waitForResponseFromServer<T>(messageID: string): Promise<T> {\n return new Promise((resolve) => {\n const waiter = (data: string) => {\n const message: WebsocketMessageClient = JSON.parse(data);\n if (message.event !== 'response') {\n this.socket.once('message', waiter);\n return;\n }\n console.log('received response from ' + messageID);\n\n if (message.id === messageID) {\n resolve(message.args);\n } else {\n this.socket.once('message', waiter);\n }\n };\n this.socket.once('message', waiter);\n });\n }\n\n public send(\n event: OGIAddonClientSentEvent,\n args: ClientSentEventTypes[OGIAddonClientSentEvent]\n ): string {\n // generate a random id\n const id = Math.random().toString(36).substring(7);\n this.socket.send(\n JSON.stringify({\n event,\n args,\n id,\n })\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n","import z, { ZodError } from 'zod';\n\nexport interface ConfigurationFile {\n [key: string]: ConfigurationOption;\n}\n\nconst configValidation = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n description: z.string().min(1),\n});\n\nexport function isStringOption(\n option: ConfigurationOption\n): option is StringOption {\n return option.type === 'string';\n}\n\nexport function isNumberOption(\n option: ConfigurationOption\n): option is NumberOption {\n return option.type === 'number';\n}\n\nexport function isBooleanOption(\n option: ConfigurationOption\n): option is BooleanOption {\n return option.type === 'boolean';\n}\n\nexport class ConfigurationBuilder {\n private options: ConfigurationOption[] = [];\n\n /**\n * Add a number option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: NumberOption) => NumberOption }\n * @returns\n */\n public addNumberOption(\n option: (option: NumberOption) => NumberOption\n ): ConfigurationBuilder {\n let newOption = new NumberOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a string option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: StringOption) => StringOption }\n */\n public addStringOption(option: (option: StringOption) => StringOption) {\n let newOption = new StringOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a boolean option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: BooleanOption) => BooleanOption }\n */\n public addBooleanOption(option: (option: BooleanOption) => BooleanOption) {\n let newOption = new BooleanOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n public build(includeFunctions: boolean): ConfigurationFile {\n let config: ConfigurationFile = {};\n this.options.forEach((option) => {\n // remove all functions from the option object\n if (!includeFunctions) {\n option = JSON.parse(JSON.stringify(option));\n const optionData = configValidation.safeParse(option);\n if (!optionData.success) {\n throw new ZodError(optionData.error.errors);\n }\n\n config[option.name] = option;\n } else {\n config[option.name] = option;\n }\n });\n return config;\n }\n}\n\nexport type ConfigurationOptionType = 'string' | 'number' | 'boolean' | 'unset';\nexport class ConfigurationOption {\n public name: string = '';\n public defaultValue: unknown = '';\n public displayName: string = '';\n public description: string = '';\n public type: ConfigurationOptionType = 'unset';\n\n /**\n * Set the name of the option. **REQUIRED**\n * @param name {string} The name of the option. This is used to reference the option in the configuration file.\n */\n setName(name: string) {\n this.name = name;\n return this;\n }\n\n /**\n * Set the display name of the option. This is used to show the user a human readable version of what the option is. **REQUIRED**\n * @param displayName {string} The display name of the option.\n * @returns\n */\n setDisplayName(displayName: string) {\n this.displayName = displayName;\n return this;\n }\n\n /**\n * Set the description of the option. This is to show the user a brief description of what this option does. **REQUIRED**\n * @param description {string} The description of the option.\n * @returns\n */\n setDescription(description: string) {\n this.description = description;\n return this;\n }\n\n /**\n * Validation code for the option. This is called when the user provides input to the option. If the validation fails, the user will be prompted to provide input again.\n * @param input {unknown} The input to validate\n */\n validate(input: unknown): [boolean, string] {\n throw new Error('Validation code not implemented. Value: ' + input);\n }\n}\n\nexport class StringOption extends ConfigurationOption {\n public allowedValues: string[] = [];\n public minTextLength: number = 0;\n public maxTextLength: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: string = '';\n public inputType: 'text' | 'file' | 'password' | 'folder' = 'text';\n public type: ConfigurationOptionType = 'string';\n\n /**\n * Set the allowed values for the string. If the array is empty, any value is allowed. When provided, the client will act like this option is a dropdown.\n * @param allowedValues {string[]} An array of allowed values for the string. If the array is empty, any value is allowed.\n */\n setAllowedValues(allowedValues: string[]): this {\n this.allowedValues = allowedValues;\n return this;\n }\n\n /**\n * Set the default value for the string. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {string} The default value for the string.\n */\n setDefaultValue(defaultValue: string): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n /**\n * Set the minimum text length for the string. If the user provides a string that is less than this value, the validation will fail.\n * @param minTextLength {number} The minimum text length for the string.\n */\n setMinTextLength(minTextLength: number): this {\n this.minTextLength = minTextLength;\n return this;\n }\n\n /**\n * Set the maximum text length for the string. If the user provides a string that is greater than this value, the validation will fail.\n * @param maxTextLength {number} The maximum text length for the string.\n */\n setMaxTextLength(maxTextLength: number): this {\n this.maxTextLength = maxTextLength;\n return this;\n }\n\n /**\n * Set the input type for the string. This will change how the client renders the input.\n * @param inputType {'text' | 'file' | 'password' | 'folder'} The input type for the string.\n */\n setInputType(inputType: 'text' | 'file' | 'password' | 'folder'): this {\n this.inputType = inputType;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'string') {\n return [false, 'Input is not a string'];\n }\n if (this.allowedValues.length === 0 && input.length !== 0)\n return [true, ''];\n if (\n input.length < this.minTextLength ||\n input.length > this.maxTextLength\n ) {\n return [\n false,\n 'Input is not within the text length ' +\n this.minTextLength +\n ' and ' +\n this.maxTextLength +\n ' characters (currently ' +\n input.length +\n ' characters)',\n ];\n }\n\n return [\n this.allowedValues.includes(input),\n 'Input is not an allowed value',\n ];\n }\n}\n\nexport class NumberOption extends ConfigurationOption {\n public min: number = 0;\n public max: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: number = 0;\n public type: ConfigurationOptionType = 'number';\n public inputType: 'range' | 'number' = 'number';\n\n /**\n * Set the minimum value for the number. If the user provides a number that is less than this value, the validation will fail.\n * @param min {number} The minimum value for the number.\n */\n setMin(min: number): this {\n this.min = min;\n return this;\n }\n\n /**\n * Set the input type for the number. This will change how the client renders the input.\n * @param type {'range' | 'number'} The input type for the number.\n */\n setInputType(type: 'range' | 'number'): this {\n this.inputType = type;\n return this;\n }\n\n /**\n * Set the maximum value for the number. If the user provides a number that is greater than this value, the validation will fail.\n * @param max {number} The maximum value for the number.\n */\n setMax(max: number): this {\n this.max = max;\n return this;\n }\n\n /**\n * Set the default value for the number. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {number} The default value for the number.\n */\n setDefaultValue(defaultValue: number): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (isNaN(Number(input))) {\n return [false, 'Input is not a number'];\n }\n if (Number(input) < this.min || Number(input) > this.max) {\n return [\n false,\n 'Input is not within the range of ' + this.min + ' and ' + this.max,\n ];\n }\n return [true, ''];\n }\n}\n\nexport class BooleanOption extends ConfigurationOption {\n public type: ConfigurationOptionType = 'boolean';\n public defaultValue: boolean = false;\n\n /**\n * Set the default value for the boolean. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {boolean} The default value for the boolean.\n */\n setDefaultValue(defaultValue: boolean): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'boolean') {\n return [false, 'Input is not a boolean'];\n }\n return [true, ''];\n }\n}\n","import {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n} from './ConfigurationBuilder';\n\ninterface DefiniteConfig {\n [key: string]: string | number | boolean;\n}\nexport class Configuration {\n readonly storedConfigTemplate: ConfigurationFile;\n definiteConfig: DefiniteConfig = {};\n constructor(configTemplate: ConfigurationFile) {\n this.storedConfigTemplate = configTemplate;\n }\n\n updateConfig(\n config: DefiniteConfig,\n validate: boolean = true\n ): [boolean, { [key: string]: string }] {\n this.definiteConfig = config;\n if (validate) {\n const result = this.validateConfig();\n return result;\n }\n return [true, {}];\n }\n // provides falsey or truthy value, and an error message if falsey\n private validateConfig(): [boolean, { [key: string]: string }] {\n const erroredKeys = new Map<string, string>();\n for (const key in this.storedConfigTemplate) {\n if (\n this.definiteConfig[key] === null ||\n this.definiteConfig[key] === undefined\n ) {\n console.warn(\n 'Option ' +\n key +\n ' is not defined. Using default value Value: ' +\n this.definiteConfig[key]\n );\n this.definiteConfig[key] = this.storedConfigTemplate[key]\n .defaultValue as string | number | boolean;\n }\n if (\n this.storedConfigTemplate[key].type !== typeof this.definiteConfig[key]\n ) {\n throw new Error('Option ' + key + ' is not of the correct type');\n }\n\n const result = this.storedConfigTemplate[key].validate(\n this.definiteConfig[key]\n );\n if (!result[0]) {\n erroredKeys.set(key, result[1]);\n }\n }\n\n for (const key in this.definiteConfig) {\n if (!this.storedConfigTemplate[key]) {\n throw new Error(\n 'Option ' + key + ' is not defined in the configuration template'\n );\n }\n }\n\n if (erroredKeys.size > 0) {\n return [false, Object.fromEntries(erroredKeys)];\n }\n\n return [true, Object.fromEntries(erroredKeys)];\n }\n\n getStringValue(optionName: string): string {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'string') {\n throw new Error('Option ' + optionName + ' is not a string');\n }\n return this.definiteConfig[optionName];\n }\n\n getNumberValue(optionName: string): number {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'number') {\n throw new Error('Option ' + optionName + ' is not a number');\n }\n return this.definiteConfig[optionName];\n }\n\n getBooleanValue(optionName: string): boolean {\n if (this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'boolean') {\n throw new Error('Option ' + optionName + ' is not a boolean');\n }\n return this.definiteConfig[optionName];\n }\n}\n\nexport {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n};\n","import { ConfigurationBuilder } from './main';\n\nexport default class EventResponse<T> {\n data: T | undefined = undefined;\n deffered: boolean = false;\n resolved: boolean = false;\n progress: number = 0;\n logs: string[] = [];\n failed: string | undefined = undefined;\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>;\n\n constructor(\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>\n ) {\n this.onInputAsked = onInputAsked;\n }\n\n public defer(promise?: () => Promise<void>) {\n this.deffered = true;\n // include this to make it easier to use the defer method with async functions\n if (promise) {\n promise();\n }\n }\n\n /**\n * Resolve the event with data. This acts like a promise resolve, and will stop the event from being processed further. **You must always call this method when you are done with the event.**\n * @param data {T}\n */\n public resolve(data: T) {\n this.resolved = true;\n this.data = data;\n }\n\n /**\n * Completes the event and resolves it, but does not return any data. **You must always call this method when you are done with the event.**\n */\n public complete() {\n this.resolved = true;\n }\n\n public fail(message: string) {\n this.resolved = true;\n this.failed = message;\n }\n\n /**\n * Logs a message to the event. This is useful for debugging and logging information to the user.\n * @param message {string}\n */\n public log(message: string) {\n this.logs.push(message);\n }\n\n /**\n * Send a screen to the client to ask for input. Use the `ConfigurationBuilder` system to build the screen. Once sent to the user, the addon cannot change the screen.\n * @async\n * @param name {string}\n * @param description {string}\n * @param screen {ConfigurationBuilder}\n * @returns {Promise<{ [key: string]: boolean | string | number }>}\n */\n public async askForInput(\n name: string,\n description: string,\n screen: ConfigurationBuilder\n ): Promise<{ [key: string]: boolean | string | number }> {\n if (!this.onInputAsked) {\n throw new Error('No input asked callback');\n }\n return await this.onInputAsked(screen, name, description);\n }\n}\n","{\n \"name\": \"ogi-addon\",\n \"module\": \"./build/main.js\",\n \"type\": \"module\",\n \"main\": \"./build/main.cjs\",\n \"version\": \"1.5.0\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"default\": \"./build/main.js\",\n \"types\": \"./build/main.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/main.cjs\",\n \"types\": \"./build/main.d.cts\"\n }\n },\n \"./config\": {\n \"import\": {\n \"default\": \"./build/config/Configuration.js\",\n \"types\": \"./build/config/Configuration.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/config/Configuration.cjs\",\n \"types\": \"./build/config/Configuration.d.cts\"\n }\n }\n },\n \"typings\": \"./build/main.d.ts\",\n \"author\": {\n \"name\": \"Nat3z\",\n \"email\": \"me@nat3z.com\",\n \"url\": \"https://nat3z.com/\"\n },\n \"dependencies\": {\n \"fuse.js\": \"^7.1.0\",\n \"ws\": \"^8.4.0\",\n \"zod\": \"^3.23.8\"\n },\n \"scripts\": {\n \"auto-build\": \"tsc -w\",\n \"build\": \"tsup --config tsup.config.js\",\n \"release\": \"bun run build && npm publish\",\n \"release-beta\": \"bun run build && npm publish --tag future\"\n },\n \"devDependencies\": {\n \"@types/minimatch\": \"^6.0.0\",\n \"@types/node\": \"^20.14.12\",\n \"@types/ws\": \"^8.4.0\",\n \"prettier\": \"^3.6.0\",\n \"tsup\": \"^8.2.3\",\n \"typescript\": \"^5.0.0\"\n }\n}\n"],"mappings":"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","names":["ws"]}
|
|
1
|
+
{"version":3,"sources":["../src/main.ts","../src/config/ConfigurationBuilder.ts","../src/config/Configuration.ts","../src/EventResponse.ts","../package.json"],"sourcesContent":["import ws, { WebSocket } from 'ws';\nimport events from 'node:events';\nimport {\n ConfigurationBuilder,\n ConfigurationFile,\n} from './config/ConfigurationBuilder';\nimport { Configuration } from './config/Configuration';\nimport EventResponse from './EventResponse';\nimport { SearchResult } from './SearchEngine';\nimport Fuse, { IFuseOptions } from 'fuse.js';\n\nexport type OGIAddonEvent =\n | 'connect'\n | 'disconnect'\n | 'configure'\n | 'authenticate'\n | 'search'\n | 'setup'\n | 'library-search'\n | 'game-details'\n | 'exit'\n | 'request-dl'\n | 'catalog';\n\nexport type OGIAddonClientSentEvent =\n | 'response'\n | 'authenticate'\n | 'configure'\n | 'defer-update'\n | 'notification'\n | 'input-asked'\n | 'get-app-details'\n | 'flag'\n | 'task-update';\n\nexport type OGIAddonServerSentEvent =\n | 'authenticate'\n | 'configure'\n | 'config-update'\n | 'search'\n | 'setup'\n | 'response'\n | 'library-search'\n | 'game-details'\n | 'request-dl'\n | 'catalog';\nexport { ConfigurationBuilder, Configuration, EventResponse, SearchResult };\nconst defaultPort = 7654;\nimport pjson from '../package.json';\nexport const VERSION = pjson.version;\n\nexport interface ClientSentEventTypes {\n response: any;\n authenticate: {\n name: string;\n id: string;\n description: string;\n version: string;\n author: string;\n };\n configure: ConfigurationFile;\n 'defer-update': {\n logs: string[];\n progress: number;\n };\n notification: Notification;\n 'input-asked': ConfigurationBuilder;\n 'task-update': {\n id: string;\n progress: number;\n logs: string[];\n finished: boolean;\n failed: string | undefined;\n };\n 'get-app-details': {\n appID: number;\n storefront: string;\n };\n flag: {\n flag: string;\n value: string | string[];\n };\n}\n\nexport type BasicLibraryInfo = {\n name: string;\n capsuleImage: string;\n appID: number;\n storefront: string;\n};\n\nexport interface EventListenerTypes {\n /**\n * This event is emitted when the addon connects to the OGI Addon Server. Addon does not need to resolve anything.\n * @param socket\n * @returns\n */\n connect: (socket: ws) => void;\n\n /**\n * 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.\n * @param reason\n * @returns\n */\n disconnect: (reason: string) => void;\n /**\n * 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)\n * @param config\n * @returns\n */\n configure: (config: ConfigurationBuilder) => ConfigurationBuilder;\n /**\n * This event is called when the client provides a response to any event. This should be treated as middleware.\n * @param response\n * @returns\n */\n response: (response: any) => void;\n\n /**\n * This event is called when the client requests for the addon to authenticate itself. You don't need to provide any info.\n * @param config\n * @returns\n */\n authenticate: (config: any) => void;\n /**\n * 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)\n * @param query\n * @param event\n * @returns\n */\n search: (\n query: { storefront: string; appID: number },\n event: EventResponse<SearchResult[]>\n ) => void;\n /**\n * 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)\n * @param data\n * @param event\n * @returns\n */\n setup: (\n data: {\n path: string;\n type: 'direct' | 'torrent' | 'magnet';\n name: string;\n usedRealDebrid: boolean;\n multiPartFiles?: {\n name: string;\n downloadURL: string;\n }[];\n appID: number;\n storefront: string;\n },\n event: EventResponse<\n Omit<\n LibraryInfo,\n | 'capsuleImage'\n | 'coverImage'\n | 'name'\n | 'appID'\n | 'storefront'\n | 'addonsource'\n | 'titleImage'\n >\n >\n ) => void;\n\n /**\n * This event is emitted when the client requires for a search to be performed. Input is the search query.\n * @param query\n * @param event\n * @returns\n */\n 'library-search': (\n query: string,\n event: EventResponse<BasicLibraryInfo[]>\n ) => void;\n\n /**\n * 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.\n * @param appID\n * @param event\n * @returns\n */\n 'game-details': (\n details: { appID: number; storefront: string },\n event: EventResponse<StoreData | undefined>\n ) => void;\n\n /**\n * 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)`.\n * @returns\n */\n exit: () => void;\n\n /**\n * 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.\n * @param appID\n * @param info\n * @param event\n * @returns\n */\n 'request-dl': (\n appID: number,\n info: SearchResult,\n event: EventResponse<SearchResult>\n ) => void;\n\n /**\n * This event is emitted when the client requests for a catalog to be fetched. Addon should resolve the event with the catalog.\n * @param event\n * @returns\n */\n catalog: (\n event: Omit<\n EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>,\n 'askForInput'\n >\n ) => void;\n}\n\nexport interface StoreData {\n name: string;\n publishers: string[];\n developers: string[];\n appID: number;\n releaseDate: string;\n capsuleImage: string;\n coverImage: string;\n basicDescription: string;\n description: string;\n headerImage: string;\n}\nexport interface WebsocketMessageClient {\n event: OGIAddonClientSentEvent;\n id?: string;\n args: any;\n}\nexport interface WebsocketMessageServer {\n event: OGIAddonServerSentEvent;\n id?: string;\n args: any;\n}\n\n/**\n * The configuration for the addon. This is used to identify the addon and provide information about it.\n * Storefronts is an array of names of stores that the addon supports.\n */\nexport interface OGIAddonConfiguration {\n name: string;\n id: string;\n description: string;\n version: string;\n\n author: string;\n repository: string;\n storefronts: string[];\n}\n\n/**\n * 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.\n * @example\n * ```typescript\n * const addon = new OGIAddon({\n * name: 'Test Addon',\n * id: 'test-addon',\n * description: 'A test addon',\n * version: '1.0.0',\n * author: 'OGI Developers',\n * repository: ''\n * });\n * ```\n *\n */\nexport default class OGIAddon {\n public eventEmitter = new events.EventEmitter();\n public addonWSListener: OGIAddonWSListener;\n public addonInfo: OGIAddonConfiguration;\n public config: Configuration = new Configuration({});\n private eventsAvailable: OGIAddonEvent[] = [];\n private registeredConnectEvent: boolean = false;\n\n constructor(addonInfo: OGIAddonConfiguration) {\n this.addonInfo = addonInfo;\n this.addonWSListener = new OGIAddonWSListener(this, this.eventEmitter);\n }\n\n /**\n * Register an event listener for the addon. (See EventListenerTypes)\n * @param event {OGIAddonEvent}\n * @param listener {EventListenerTypes[OGIAddonEvent]}\n */\n public on<T extends OGIAddonEvent>(\n event: T,\n listener: EventListenerTypes[T]\n ) {\n this.eventEmitter.on(event, listener);\n this.eventsAvailable.push(event);\n // wait for the addon to be connected\n if (!this.registeredConnectEvent) {\n this.addonWSListener.eventEmitter.once('connect', () => {\n this.addonWSListener.send('flag', {\n flag: 'events-available',\n value: this.eventsAvailable,\n });\n });\n this.registeredConnectEvent = true;\n }\n }\n\n public emit<T extends OGIAddonEvent>(\n event: T,\n ...args: Parameters<EventListenerTypes[T]>\n ) {\n this.eventEmitter.emit(event, ...args);\n }\n\n /**\n * Notify the client using a notification. Provide the type of notification, the message, and an ID.\n * @param notification {Notification}\n */\n public notify(notification: Notification) {\n this.addonWSListener.send('notification', [notification]);\n }\n\n /**\n * Get the app details for a given appID and storefront.\n * @param appID {number}\n * @param storefront {string}\n * @returns {Promise<StoreData>}\n */\n public async getAppDetails(appID: number, storefront: string) {\n const id = this.addonWSListener.send('get-app-details', {\n appID,\n storefront,\n });\n return await this.addonWSListener.waitForResponseFromServer<\n StoreData | undefined\n >(id);\n }\n\n /**\n * Notify the OGI Addon Server that you are performing a background task. This can be used to help users understand what is happening in the background.\n * @param id {string}\n * @param progress {number}\n * @param logs {string[]}\n */\n public async task() {\n const id = Math.random().toString(36).substring(7);\n const progress = 0;\n const logs: string[] = [];\n const task = new CustomTask(this.addonWSListener, id, progress, logs);\n this.addonWSListener.send('task-update', {\n id,\n progress,\n logs,\n finished: false,\n failed: undefined,\n });\n return task;\n }\n}\n\nexport class CustomTask {\n public readonly id: string;\n public progress: number;\n public logs: string[];\n public finished: boolean = false;\n public ws: OGIAddonWSListener;\n public failed: string | undefined = undefined;\n constructor(\n ws: OGIAddonWSListener,\n id: string,\n progress: number,\n logs: string[]\n ) {\n this.id = id;\n this.progress = progress;\n this.logs = logs;\n this.ws = ws;\n }\n public log(log: string) {\n this.logs.push(log);\n this.update();\n }\n public finish() {\n this.finished = true;\n this.update();\n }\n public fail(message: string) {\n this.failed = message;\n this.update();\n }\n public setProgress(progress: number) {\n this.progress = progress;\n this.update();\n }\n public update() {\n this.ws.send('task-update', {\n id: this.id,\n progress: this.progress,\n logs: this.logs,\n finished: this.finished,\n failed: this.failed,\n });\n }\n}\n/**\n * A search tool wrapper over Fuse.js for the OGI Addon. This tool is used to search for items in the library.\n * @example\n * ```typescript\n * const searchTool = new SearchTool<LibraryInfo>([{ name: 'test', appID: 123 }, { name: 'test2', appID: 124 }], ['name']);\n * const results = searchTool.search('test', 10);\n * ```\n */\nexport class SearchTool<T> {\n private fuse: Fuse<T>;\n constructor(\n items: T[],\n keys: string[],\n options: Omit<IFuseOptions<T>, 'keys'> = {\n threshold: 0.3,\n includeScore: true,\n }\n ) {\n this.fuse = new Fuse(items, {\n keys,\n ...options,\n });\n }\n public search(query: string, limit: number = 10): T[] {\n return this.fuse\n .search(query)\n .slice(0, limit)\n .map((result) => result.item);\n }\n public addItems(items: T[]) {\n items.map((item) => this.fuse.add(item));\n }\n}\n/**\n * Library Info is the metadata for a library entry after setting up a game.\n */\nexport interface LibraryInfo {\n name: string;\n version: string;\n cwd: string;\n appID: number;\n launchExecutable: string;\n launchArguments?: string;\n capsuleImage: string;\n storefront: string;\n addonsource: string;\n coverImage: string;\n titleImage?: string;\n}\ninterface Notification {\n type: 'warning' | 'error' | 'info' | 'success';\n message: string;\n id: string;\n}\nclass OGIAddonWSListener {\n private socket: WebSocket;\n public eventEmitter: events.EventEmitter;\n public addon: OGIAddon;\n\n constructor(ogiAddon: OGIAddon, eventEmitter: events.EventEmitter) {\n if (\n process.argv[process.argv.length - 1].split('=')[0] !== '--addonSecret'\n ) {\n throw new Error(\n 'No secret provided. This usually happens because the addon was not started by the OGI Addon Server.'\n );\n }\n this.addon = ogiAddon;\n this.eventEmitter = eventEmitter;\n this.socket = new ws('ws://localhost:' + defaultPort);\n this.socket.on('open', () => {\n console.log('Connected to OGI Addon Server');\n console.log('OGI Addon Server Version:', VERSION);\n\n // Authenticate with OGI Addon Server\n this.send('authenticate', {\n ...this.addon.addonInfo,\n secret: process.argv[process.argv.length - 1].split('=')[1],\n ogiVersion: VERSION,\n });\n\n this.eventEmitter.emit('connect');\n\n // send a configuration request\n let configBuilder = new ConfigurationBuilder();\n this.eventEmitter.emit('configure', configBuilder);\n this.send('configure', configBuilder.build(false));\n this.addon.config = new Configuration(configBuilder.build(true));\n });\n\n this.socket.on('error', (error) => {\n if (error.message.includes('Failed to connect')) {\n throw new Error(\n 'OGI Addon Server is not running/is unreachable. Please start the server and try again.'\n );\n }\n console.error('An error occurred:', error);\n });\n\n this.socket.on('close', (code, reason) => {\n if (code === 1008) {\n console.error('Authentication failed:', reason);\n return;\n }\n this.eventEmitter.emit('disconnect', reason);\n console.log('Disconnected from OGI Addon Server');\n console.error(reason.toString());\n this.eventEmitter.emit('exit');\n this.socket.close();\n });\n\n this.registerMessageReceiver();\n }\n\n private async userInputAsked(\n configBuilt: ConfigurationBuilder,\n name: string,\n description: string,\n socket: WebSocket\n ): Promise<{ [key: string]: number | boolean | string }> {\n const config = configBuilt.build(false);\n const id = Math.random().toString(36).substring(7);\n if (!socket) {\n return {};\n }\n socket.send(\n JSON.stringify({\n event: 'input-asked',\n args: {\n config,\n name,\n description,\n },\n id: id,\n })\n );\n return await this.waitForResponseFromServer(id);\n }\n\n private registerMessageReceiver() {\n this.socket.on('message', async (data: string) => {\n const message: WebsocketMessageServer = JSON.parse(data);\n switch (message.event) {\n case 'config-update':\n const result = this.addon.config.updateConfig(message.args);\n if (!result[0]) {\n this.respondToMessage(\n message.id!!,\n {\n success: false,\n error: result[1],\n },\n undefined\n );\n } else {\n this.respondToMessage(message.id!!, { success: true }, undefined);\n }\n break;\n case 'search':\n let searchResultEvent = new EventResponse<SearchResult[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit('search', message.args, searchResultEvent);\n const searchResult =\n await this.waitForEventToRespond(searchResultEvent);\n this.respondToMessage(\n message.id!!,\n searchResult.data,\n searchResultEvent\n );\n break;\n case 'setup':\n let setupEvent = new EventResponse<LibraryInfo>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n this.eventEmitter.emit(\n 'setup',\n {\n path: message.args.path,\n appID: message.args.appID,\n storefront: message.args.storefront,\n type: message.args.type,\n name: message.args.name,\n usedRealDebrid: message.args.usedRealDebrid,\n multiPartFiles: message.args.multiPartFiles,\n },\n setupEvent\n );\n const interval = setInterval(() => {\n if (setupEvent.resolved) {\n clearInterval(interval);\n return;\n }\n this.send('defer-update', {\n logs: setupEvent.logs,\n deferID: message.args.deferID,\n progress: setupEvent.progress,\n failed: setupEvent.failed,\n } as ClientSentEventTypes['defer-update']);\n }, 100);\n const setupResult = await this.waitForEventToRespond(setupEvent);\n this.respondToMessage(message.id!!, setupResult.data, setupEvent);\n break;\n case 'library-search':\n let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(message.id!!, [], librarySearchEvent);\n break;\n }\n this.eventEmitter.emit(\n 'library-search',\n message.args,\n librarySearchEvent\n );\n const librarySearchResult =\n await this.waitForEventToRespond(librarySearchEvent);\n this.respondToMessage(\n message.id!!,\n librarySearchResult.data,\n librarySearchEvent\n );\n break;\n case 'game-details':\n let gameDetailsEvent = new EventResponse<StoreData | undefined>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('game-details') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for game-details',\n },\n gameDetailsEvent\n );\n break;\n }\n this.eventEmitter.emit(\n 'game-details',\n message.args,\n gameDetailsEvent\n );\n const gameDetailsResult =\n await this.waitForEventToRespond(gameDetailsEvent);\n this.respondToMessage(\n message.id!!,\n gameDetailsResult.data,\n gameDetailsEvent\n );\n break;\n case 'request-dl':\n let requestDLEvent = new EventResponse<SearchResult>(\n (screen, name, description) =>\n this.userInputAsked(screen, name, description, this.socket)\n );\n if (this.eventEmitter.listenerCount('request-dl') === 0) {\n this.respondToMessage(\n message.id!!,\n {\n error: 'No event listener for request-dl',\n },\n requestDLEvent\n );\n break;\n }\n this.eventEmitter.emit(\n 'request-dl',\n message.args.appID,\n message.args.info,\n requestDLEvent\n );\n const requestDLResult =\n await this.waitForEventToRespond(requestDLEvent);\n if (requestDLEvent.failed) {\n this.respondToMessage(message.id!!, undefined, requestDLEvent);\n break;\n }\n if (\n requestDLEvent.data === undefined ||\n requestDLEvent.data?.downloadType === 'request'\n ) {\n throw new Error(\n 'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'\n );\n }\n this.respondToMessage(\n message.id!!,\n requestDLResult.data,\n requestDLEvent\n );\n break;\n case 'catalog':\n let catalogEvent = new EventResponse<{\n [key: string]: {\n name: string;\n description: string;\n listings: BasicLibraryInfo[];\n };\n }>();\n this.eventEmitter.emit('catalog', catalogEvent);\n const catalogResult = await this.waitForEventToRespond(catalogEvent);\n this.respondToMessage(message.id!!, catalogResult.data, catalogEvent);\n break;\n }\n });\n }\n\n private waitForEventToRespond<T>(\n event: EventResponse<T>\n ): Promise<EventResponse<T>> {\n // check the handlers to see if there even is any\n return new Promise((resolve, reject) => {\n const dataGet = setInterval(() => {\n if (event.resolved) {\n resolve(event);\n clearTimeout(timeout);\n }\n }, 5);\n\n const timeout = setTimeout(() => {\n if (event.deffered) {\n clearInterval(dataGet);\n const interval = setInterval(() => {\n if (event.resolved) {\n clearInterval(interval);\n resolve(event);\n }\n }, 100);\n } else {\n reject('Event did not respond in time');\n }\n }, 5000);\n });\n }\n\n public respondToMessage(\n messageID: string,\n response: any,\n originalEvent: EventResponse<any> | undefined\n ) {\n this.socket.send(\n JSON.stringify({\n event: 'response',\n id: messageID,\n args: response,\n statusError: originalEvent ? originalEvent.failed : undefined,\n })\n );\n console.log('dispatched response to ' + messageID);\n }\n\n public waitForResponseFromServer<T>(messageID: string): Promise<T> {\n return new Promise((resolve) => {\n const waiter = (data: string) => {\n const message: WebsocketMessageClient = JSON.parse(data);\n if (message.event !== 'response') {\n this.socket.once('message', waiter);\n return;\n }\n console.log('received response from ' + messageID);\n\n if (message.id === messageID) {\n resolve(message.args);\n } else {\n this.socket.once('message', waiter);\n }\n };\n this.socket.once('message', waiter);\n });\n }\n\n public send(\n event: OGIAddonClientSentEvent,\n args: ClientSentEventTypes[OGIAddonClientSentEvent]\n ): string {\n // generate a random id\n const id = Math.random().toString(36).substring(7);\n this.socket.send(\n JSON.stringify({\n event,\n args,\n id,\n })\n );\n return id;\n }\n\n public close() {\n this.socket.close();\n }\n}\n","import z, { ZodError } from 'zod';\n\nexport interface ConfigurationFile {\n [key: string]: ConfigurationOption;\n}\n\nconst configValidation = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n description: z.string().min(1),\n});\n\nexport function isStringOption(\n option: ConfigurationOption\n): option is StringOption {\n return option.type === 'string';\n}\n\nexport function isNumberOption(\n option: ConfigurationOption\n): option is NumberOption {\n return option.type === 'number';\n}\n\nexport function isBooleanOption(\n option: ConfigurationOption\n): option is BooleanOption {\n return option.type === 'boolean';\n}\n\nexport class ConfigurationBuilder {\n private options: ConfigurationOption[] = [];\n\n /**\n * Add a number option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: NumberOption) => NumberOption }\n * @returns\n */\n public addNumberOption(\n option: (option: NumberOption) => NumberOption\n ): ConfigurationBuilder {\n let newOption = new NumberOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a string option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: StringOption) => StringOption }\n */\n public addStringOption(option: (option: StringOption) => StringOption) {\n let newOption = new StringOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n /**\n * Add a boolean option to the configuration builder and return the builder for chaining. You must provide a name, display name, and description for the option.\n * @param option { (option: BooleanOption) => BooleanOption }\n */\n public addBooleanOption(option: (option: BooleanOption) => BooleanOption) {\n let newOption = new BooleanOption();\n newOption = option(newOption);\n this.options.push(newOption);\n return this;\n }\n\n public build(includeFunctions: boolean): ConfigurationFile {\n let config: ConfigurationFile = {};\n this.options.forEach((option) => {\n // remove all functions from the option object\n if (!includeFunctions) {\n option = JSON.parse(JSON.stringify(option));\n const optionData = configValidation.safeParse(option);\n if (!optionData.success) {\n throw new ZodError(optionData.error.errors);\n }\n\n config[option.name] = option;\n } else {\n config[option.name] = option;\n }\n });\n return config;\n }\n}\n\nexport type ConfigurationOptionType = 'string' | 'number' | 'boolean' | 'unset';\nexport class ConfigurationOption {\n public name: string = '';\n public defaultValue: unknown = '';\n public displayName: string = '';\n public description: string = '';\n public type: ConfigurationOptionType = 'unset';\n\n /**\n * Set the name of the option. **REQUIRED**\n * @param name {string} The name of the option. This is used to reference the option in the configuration file.\n */\n setName(name: string) {\n this.name = name;\n return this;\n }\n\n /**\n * Set the display name of the option. This is used to show the user a human readable version of what the option is. **REQUIRED**\n * @param displayName {string} The display name of the option.\n * @returns\n */\n setDisplayName(displayName: string) {\n this.displayName = displayName;\n return this;\n }\n\n /**\n * Set the description of the option. This is to show the user a brief description of what this option does. **REQUIRED**\n * @param description {string} The description of the option.\n * @returns\n */\n setDescription(description: string) {\n this.description = description;\n return this;\n }\n\n /**\n * Validation code for the option. This is called when the user provides input to the option. If the validation fails, the user will be prompted to provide input again.\n * @param input {unknown} The input to validate\n */\n validate(input: unknown): [boolean, string] {\n throw new Error('Validation code not implemented. Value: ' + input);\n }\n}\n\nexport class StringOption extends ConfigurationOption {\n public allowedValues: string[] = [];\n public minTextLength: number = 0;\n public maxTextLength: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: string = '';\n public inputType: 'text' | 'file' | 'password' | 'folder' = 'text';\n public type: ConfigurationOptionType = 'string';\n\n /**\n * Set the allowed values for the string. If the array is empty, any value is allowed. When provided, the client will act like this option is a dropdown.\n * @param allowedValues {string[]} An array of allowed values for the string. If the array is empty, any value is allowed.\n */\n setAllowedValues(allowedValues: string[]): this {\n this.allowedValues = allowedValues;\n return this;\n }\n\n /**\n * Set the default value for the string. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {string} The default value for the string.\n */\n setDefaultValue(defaultValue: string): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n /**\n * Set the minimum text length for the string. If the user provides a string that is less than this value, the validation will fail.\n * @param minTextLength {number} The minimum text length for the string.\n */\n setMinTextLength(minTextLength: number): this {\n this.minTextLength = minTextLength;\n return this;\n }\n\n /**\n * Set the maximum text length for the string. If the user provides a string that is greater than this value, the validation will fail.\n * @param maxTextLength {number} The maximum text length for the string.\n */\n setMaxTextLength(maxTextLength: number): this {\n this.maxTextLength = maxTextLength;\n return this;\n }\n\n /**\n * Set the input type for the string. This will change how the client renders the input.\n * @param inputType {'text' | 'file' | 'password' | 'folder'} The input type for the string.\n */\n setInputType(inputType: 'text' | 'file' | 'password' | 'folder'): this {\n this.inputType = inputType;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'string') {\n return [false, 'Input is not a string'];\n }\n if (this.allowedValues.length === 0 && input.length !== 0)\n return [true, ''];\n if (\n input.length < this.minTextLength ||\n input.length > this.maxTextLength\n ) {\n return [\n false,\n 'Input is not within the text length ' +\n this.minTextLength +\n ' and ' +\n this.maxTextLength +\n ' characters (currently ' +\n input.length +\n ' characters)',\n ];\n }\n\n return [\n this.allowedValues.includes(input),\n 'Input is not an allowed value',\n ];\n }\n}\n\nexport class NumberOption extends ConfigurationOption {\n public min: number = 0;\n public max: number = Number.MAX_SAFE_INTEGER;\n public defaultValue: number = 0;\n public type: ConfigurationOptionType = 'number';\n public inputType: 'range' | 'number' = 'number';\n\n /**\n * Set the minimum value for the number. If the user provides a number that is less than this value, the validation will fail.\n * @param min {number} The minimum value for the number.\n */\n setMin(min: number): this {\n this.min = min;\n return this;\n }\n\n /**\n * Set the input type for the number. This will change how the client renders the input.\n * @param type {'range' | 'number'} The input type for the number.\n */\n setInputType(type: 'range' | 'number'): this {\n this.inputType = type;\n return this;\n }\n\n /**\n * Set the maximum value for the number. If the user provides a number that is greater than this value, the validation will fail.\n * @param max {number} The maximum value for the number.\n */\n setMax(max: number): this {\n this.max = max;\n return this;\n }\n\n /**\n * Set the default value for the number. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {number} The default value for the number.\n */\n setDefaultValue(defaultValue: number): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (isNaN(Number(input))) {\n return [false, 'Input is not a number'];\n }\n if (Number(input) < this.min || Number(input) > this.max) {\n return [\n false,\n 'Input is not within the range of ' + this.min + ' and ' + this.max,\n ];\n }\n return [true, ''];\n }\n}\n\nexport class BooleanOption extends ConfigurationOption {\n public type: ConfigurationOptionType = 'boolean';\n public defaultValue: boolean = false;\n\n /**\n * Set the default value for the boolean. This value will be used if the user does not provide a value. **HIGHLY RECOMMENDED**\n * @param defaultValue {boolean} The default value for the boolean.\n */\n setDefaultValue(defaultValue: boolean): this {\n this.defaultValue = defaultValue;\n return this;\n }\n\n override validate(input: unknown): [boolean, string] {\n if (typeof input !== 'boolean') {\n return [false, 'Input is not a boolean'];\n }\n return [true, ''];\n }\n}\n","import {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n} from './ConfigurationBuilder';\n\ninterface DefiniteConfig {\n [key: string]: string | number | boolean;\n}\nexport class Configuration {\n readonly storedConfigTemplate: ConfigurationFile;\n definiteConfig: DefiniteConfig = {};\n constructor(configTemplate: ConfigurationFile) {\n this.storedConfigTemplate = configTemplate;\n }\n\n updateConfig(\n config: DefiniteConfig,\n validate: boolean = true\n ): [boolean, { [key: string]: string }] {\n this.definiteConfig = config;\n if (validate) {\n const result = this.validateConfig();\n return result;\n }\n return [true, {}];\n }\n // provides falsey or truthy value, and an error message if falsey\n private validateConfig(): [boolean, { [key: string]: string }] {\n const erroredKeys = new Map<string, string>();\n for (const key in this.storedConfigTemplate) {\n if (\n this.definiteConfig[key] === null ||\n this.definiteConfig[key] === undefined\n ) {\n console.warn(\n 'Option ' +\n key +\n ' is not defined. Using default value Value: ' +\n this.definiteConfig[key]\n );\n this.definiteConfig[key] = this.storedConfigTemplate[key]\n .defaultValue as string | number | boolean;\n }\n if (\n this.storedConfigTemplate[key].type !== typeof this.definiteConfig[key]\n ) {\n throw new Error('Option ' + key + ' is not of the correct type');\n }\n\n const result = this.storedConfigTemplate[key].validate(\n this.definiteConfig[key]\n );\n if (!result[0]) {\n erroredKeys.set(key, result[1]);\n }\n }\n\n for (const key in this.definiteConfig) {\n if (!this.storedConfigTemplate[key]) {\n throw new Error(\n 'Option ' + key + ' is not defined in the configuration template'\n );\n }\n }\n\n if (erroredKeys.size > 0) {\n return [false, Object.fromEntries(erroredKeys)];\n }\n\n return [true, Object.fromEntries(erroredKeys)];\n }\n\n getStringValue(optionName: string): string {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'string') {\n throw new Error('Option ' + optionName + ' is not a string');\n }\n return this.definiteConfig[optionName];\n }\n\n getNumberValue(optionName: string): number {\n if (!this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'number') {\n throw new Error('Option ' + optionName + ' is not a number');\n }\n return this.definiteConfig[optionName];\n }\n\n getBooleanValue(optionName: string): boolean {\n if (this.definiteConfig[optionName] === null) {\n throw new Error('Option ' + optionName + ' is not defined');\n }\n if (typeof this.definiteConfig[optionName] !== 'boolean') {\n throw new Error('Option ' + optionName + ' is not a boolean');\n }\n return this.definiteConfig[optionName];\n }\n}\n\nexport {\n ConfigurationFile,\n ConfigurationBuilder,\n BooleanOption,\n ConfigurationOption,\n ConfigurationOptionType,\n NumberOption,\n StringOption,\n isBooleanOption,\n isNumberOption,\n isStringOption,\n};\n","import { ConfigurationBuilder } from './main';\n\nexport default class EventResponse<T> {\n data: T | undefined = undefined;\n deffered: boolean = false;\n resolved: boolean = false;\n progress: number = 0;\n logs: string[] = [];\n failed: string | undefined = undefined;\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>;\n\n constructor(\n onInputAsked?: (\n screen: ConfigurationBuilder,\n name: string,\n description: string\n ) => Promise<{ [key: string]: boolean | string | number }>\n ) {\n this.onInputAsked = onInputAsked;\n }\n\n public defer(promise?: () => Promise<void>) {\n this.deffered = true;\n // include this to make it easier to use the defer method with async functions\n if (promise) {\n promise();\n }\n }\n\n /**\n * Resolve the event with data. This acts like a promise resolve, and will stop the event from being processed further. **You must always call this method when you are done with the event.**\n * @param data {T}\n */\n public resolve(data: T) {\n this.resolved = true;\n this.data = data;\n }\n\n /**\n * Completes the event and resolves it, but does not return any data. **You must always call this method when you are done with the event.**\n */\n public complete() {\n this.resolved = true;\n }\n\n public fail(message: string) {\n this.resolved = true;\n this.failed = message;\n }\n\n /**\n * Logs a message to the event. This is useful for debugging and logging information to the user.\n * @param message {string}\n */\n public log(message: string) {\n this.logs.push(message);\n }\n\n /**\n * Send a screen to the client to ask for input. Use the `ConfigurationBuilder` system to build the screen. Once sent to the user, the addon cannot change the screen.\n * @async\n * @param name {string}\n * @param description {string}\n * @param screen {ConfigurationBuilder}\n * @returns {Promise<{ [key: string]: boolean | string | number }>}\n */\n public async askForInput(\n name: string,\n description: string,\n screen: ConfigurationBuilder\n ): Promise<{ [key: string]: boolean | string | number }> {\n if (!this.onInputAsked) {\n throw new Error('No input asked callback');\n }\n return await this.onInputAsked(screen, name, description);\n }\n}\n","{\n \"name\": \"ogi-addon\",\n \"module\": \"./build/main.js\",\n \"type\": \"module\",\n \"main\": \"./build/main.cjs\",\n \"version\": \"1.5.2\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"default\": \"./build/main.js\",\n \"types\": \"./build/main.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/main.cjs\",\n \"types\": \"./build/main.d.cts\"\n }\n },\n \"./config\": {\n \"import\": {\n \"default\": \"./build/config/Configuration.js\",\n \"types\": \"./build/config/Configuration.d.ts\"\n },\n \"require\": {\n \"default\": \"./build/config/Configuration.cjs\",\n \"types\": \"./build/config/Configuration.d.cts\"\n }\n }\n },\n \"typings\": \"./build/main.d.ts\",\n \"author\": {\n \"name\": \"Nat3z\",\n \"email\": \"me@nat3z.com\",\n \"url\": \"https://nat3z.com/\"\n },\n \"dependencies\": {\n \"fuse.js\": \"^7.1.0\",\n \"ws\": \"^8.4.0\",\n \"zod\": \"^3.23.8\"\n },\n \"scripts\": {\n \"auto-build\": \"tsc -w\",\n \"build\": \"tsup --config tsup.config.js\",\n \"release\": \"bun run build && npm publish\",\n \"release-beta\": \"bun run build && npm publish --tag future\"\n },\n \"devDependencies\": {\n \"@types/minimatch\": \"^6.0.0\",\n \"@types/node\": \"^20.14.12\",\n \"@types/ws\": \"^8.4.0\",\n \"prettier\": \"^3.6.0\",\n \"tsup\": \"^8.2.3\",\n \"typescript\": \"^5.0.0\"\n }\n}\n"],"mappings":"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","names":["ws"]}
|
package/package.json
CHANGED
package/src/SearchEngine.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -557,12 +557,16 @@ class OGIAddonWSListener {
|
|
|
557
557
|
case 'config-update':
|
|
558
558
|
const result = this.addon.config.updateConfig(message.args);
|
|
559
559
|
if (!result[0]) {
|
|
560
|
-
this.respondToMessage(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
560
|
+
this.respondToMessage(
|
|
561
|
+
message.id!!,
|
|
562
|
+
{
|
|
563
|
+
success: false,
|
|
564
|
+
error: result[1],
|
|
565
|
+
},
|
|
566
|
+
undefined
|
|
567
|
+
);
|
|
564
568
|
} else {
|
|
565
|
-
this.respondToMessage(message.id!!, { success: true });
|
|
569
|
+
this.respondToMessage(message.id!!, { success: true }, undefined);
|
|
566
570
|
}
|
|
567
571
|
break;
|
|
568
572
|
case 'search':
|
|
@@ -573,7 +577,11 @@ class OGIAddonWSListener {
|
|
|
573
577
|
this.eventEmitter.emit('search', message.args, searchResultEvent);
|
|
574
578
|
const searchResult =
|
|
575
579
|
await this.waitForEventToRespond(searchResultEvent);
|
|
576
|
-
this.respondToMessage(
|
|
580
|
+
this.respondToMessage(
|
|
581
|
+
message.id!!,
|
|
582
|
+
searchResult.data,
|
|
583
|
+
searchResultEvent
|
|
584
|
+
);
|
|
577
585
|
break;
|
|
578
586
|
case 'setup':
|
|
579
587
|
let setupEvent = new EventResponse<LibraryInfo>(
|
|
@@ -606,7 +614,7 @@ class OGIAddonWSListener {
|
|
|
606
614
|
} as ClientSentEventTypes['defer-update']);
|
|
607
615
|
}, 100);
|
|
608
616
|
const setupResult = await this.waitForEventToRespond(setupEvent);
|
|
609
|
-
this.respondToMessage(message.id!!, setupResult.data);
|
|
617
|
+
this.respondToMessage(message.id!!, setupResult.data, setupEvent);
|
|
610
618
|
break;
|
|
611
619
|
case 'library-search':
|
|
612
620
|
let librarySearchEvent = new EventResponse<BasicLibraryInfo[]>(
|
|
@@ -614,7 +622,7 @@ class OGIAddonWSListener {
|
|
|
614
622
|
this.userInputAsked(screen, name, description, this.socket)
|
|
615
623
|
);
|
|
616
624
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
617
|
-
this.respondToMessage(message.id!!, []);
|
|
625
|
+
this.respondToMessage(message.id!!, [], librarySearchEvent);
|
|
618
626
|
break;
|
|
619
627
|
}
|
|
620
628
|
this.eventEmitter.emit(
|
|
@@ -624,7 +632,11 @@ class OGIAddonWSListener {
|
|
|
624
632
|
);
|
|
625
633
|
const librarySearchResult =
|
|
626
634
|
await this.waitForEventToRespond(librarySearchEvent);
|
|
627
|
-
this.respondToMessage(
|
|
635
|
+
this.respondToMessage(
|
|
636
|
+
message.id!!,
|
|
637
|
+
librarySearchResult.data,
|
|
638
|
+
librarySearchEvent
|
|
639
|
+
);
|
|
628
640
|
break;
|
|
629
641
|
case 'game-details':
|
|
630
642
|
let gameDetailsEvent = new EventResponse<StoreData | undefined>(
|
|
@@ -632,9 +644,13 @@ class OGIAddonWSListener {
|
|
|
632
644
|
this.userInputAsked(screen, name, description, this.socket)
|
|
633
645
|
);
|
|
634
646
|
if (this.eventEmitter.listenerCount('game-details') === 0) {
|
|
635
|
-
this.respondToMessage(
|
|
636
|
-
|
|
637
|
-
|
|
647
|
+
this.respondToMessage(
|
|
648
|
+
message.id!!,
|
|
649
|
+
{
|
|
650
|
+
error: 'No event listener for game-details',
|
|
651
|
+
},
|
|
652
|
+
gameDetailsEvent
|
|
653
|
+
);
|
|
638
654
|
break;
|
|
639
655
|
}
|
|
640
656
|
this.eventEmitter.emit(
|
|
@@ -644,7 +660,11 @@ class OGIAddonWSListener {
|
|
|
644
660
|
);
|
|
645
661
|
const gameDetailsResult =
|
|
646
662
|
await this.waitForEventToRespond(gameDetailsEvent);
|
|
647
|
-
this.respondToMessage(
|
|
663
|
+
this.respondToMessage(
|
|
664
|
+
message.id!!,
|
|
665
|
+
gameDetailsResult.data,
|
|
666
|
+
gameDetailsEvent
|
|
667
|
+
);
|
|
648
668
|
break;
|
|
649
669
|
case 'request-dl':
|
|
650
670
|
let requestDLEvent = new EventResponse<SearchResult>(
|
|
@@ -652,9 +672,13 @@ class OGIAddonWSListener {
|
|
|
652
672
|
this.userInputAsked(screen, name, description, this.socket)
|
|
653
673
|
);
|
|
654
674
|
if (this.eventEmitter.listenerCount('request-dl') === 0) {
|
|
655
|
-
this.respondToMessage(
|
|
656
|
-
|
|
657
|
-
|
|
675
|
+
this.respondToMessage(
|
|
676
|
+
message.id!!,
|
|
677
|
+
{
|
|
678
|
+
error: 'No event listener for request-dl',
|
|
679
|
+
},
|
|
680
|
+
requestDLEvent
|
|
681
|
+
);
|
|
658
682
|
break;
|
|
659
683
|
}
|
|
660
684
|
this.eventEmitter.emit(
|
|
@@ -666,9 +690,7 @@ class OGIAddonWSListener {
|
|
|
666
690
|
const requestDLResult =
|
|
667
691
|
await this.waitForEventToRespond(requestDLEvent);
|
|
668
692
|
if (requestDLEvent.failed) {
|
|
669
|
-
this.respondToMessage(message.id!!,
|
|
670
|
-
statusError: requestDLEvent.failed,
|
|
671
|
-
});
|
|
693
|
+
this.respondToMessage(message.id!!, undefined, requestDLEvent);
|
|
672
694
|
break;
|
|
673
695
|
}
|
|
674
696
|
if (
|
|
@@ -679,7 +701,11 @@ class OGIAddonWSListener {
|
|
|
679
701
|
'Request DL event did not return a valid result. Please ensure that the event does not resolve with another `request` download type.'
|
|
680
702
|
);
|
|
681
703
|
}
|
|
682
|
-
this.respondToMessage(
|
|
704
|
+
this.respondToMessage(
|
|
705
|
+
message.id!!,
|
|
706
|
+
requestDLResult.data,
|
|
707
|
+
requestDLEvent
|
|
708
|
+
);
|
|
683
709
|
break;
|
|
684
710
|
case 'catalog':
|
|
685
711
|
let catalogEvent = new EventResponse<{
|
|
@@ -691,7 +717,7 @@ class OGIAddonWSListener {
|
|
|
691
717
|
}>();
|
|
692
718
|
this.eventEmitter.emit('catalog', catalogEvent);
|
|
693
719
|
const catalogResult = await this.waitForEventToRespond(catalogEvent);
|
|
694
|
-
this.respondToMessage(message.id!!, catalogResult.data);
|
|
720
|
+
this.respondToMessage(message.id!!, catalogResult.data, catalogEvent);
|
|
695
721
|
break;
|
|
696
722
|
}
|
|
697
723
|
});
|
|
@@ -725,12 +751,17 @@ class OGIAddonWSListener {
|
|
|
725
751
|
});
|
|
726
752
|
}
|
|
727
753
|
|
|
728
|
-
public respondToMessage(
|
|
754
|
+
public respondToMessage(
|
|
755
|
+
messageID: string,
|
|
756
|
+
response: any,
|
|
757
|
+
originalEvent: EventResponse<any> | undefined
|
|
758
|
+
) {
|
|
729
759
|
this.socket.send(
|
|
730
760
|
JSON.stringify({
|
|
731
761
|
event: 'response',
|
|
732
762
|
id: messageID,
|
|
733
763
|
args: response,
|
|
764
|
+
statusError: originalEvent ? originalEvent.failed : undefined,
|
|
734
765
|
})
|
|
735
766
|
);
|
|
736
767
|
console.log('dispatched response to ' + messageID);
|