@rimori/client 2.5.32 → 2.5.33-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/scripts/init/dev-registration.js +118 -136
- package/dist/cli/scripts/init/main.js +116 -127
- package/dist/cli/scripts/init/package-setup.js +15 -2
- package/dist/cli/scripts/release/detect-translation-languages.js +24 -35
- package/dist/cli/scripts/release/release-config-upload.js +87 -100
- package/dist/cli/scripts/release/release-db-update.js +70 -81
- package/dist/cli/scripts/release/release-file-upload.js +75 -91
- package/dist/cli/scripts/release/release-prompts-upload.js +60 -72
- package/dist/cli/scripts/release/release.js +20 -31
- package/dist/controller/AccomplishmentController.js +12 -12
- package/dist/controller/AudioController.js +15 -33
- package/dist/controller/TranslationController.js +108 -118
- package/dist/fromRimori/EventBus.js +20 -31
- package/dist/plugin/CommunicationHandler.js +73 -81
- package/dist/plugin/Logger.js +71 -83
- package/dist/plugin/RimoriClient.js +31 -31
- package/dist/plugin/StandaloneClient.js +81 -98
- package/dist/plugin/TTS/ChunkedAudioPlayer.js +31 -41
- package/dist/plugin/TTS/MessageSender.js +28 -37
- package/dist/plugin/module/AIModule.js +215 -237
- package/dist/plugin/module/DbModule.js +22 -31
- package/dist/plugin/module/EventModule.js +23 -32
- package/dist/plugin/module/ExerciseModule.js +42 -56
- package/dist/plugin/module/PluginModule.js +97 -106
- package/dist/plugin/module/SharedContentController.js +170 -207
- package/dist/plugin/module/StorageModule.js +18 -29
- package/dist/worker/WorkerSetup.js +23 -34
- package/package.json +1 -1
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { AccomplishmentController } from '../../controller/AccomplishmentController';
|
|
11
2
|
import { EventBus } from '../../fromRimori/EventBus';
|
|
12
3
|
/**
|
|
@@ -14,10 +5,14 @@ import { EventBus } from '../../fromRimori/EventBus';
|
|
|
14
5
|
* Provides methods for emitting, listening to, and responding to events.
|
|
15
6
|
*/
|
|
16
7
|
export class EventModule {
|
|
8
|
+
pluginId;
|
|
9
|
+
accomplishmentController;
|
|
10
|
+
aiModule;
|
|
11
|
+
eventBus;
|
|
17
12
|
constructor(pluginId, aiModule, eventBus) {
|
|
18
13
|
this.pluginId = pluginId;
|
|
19
14
|
this.aiModule = aiModule;
|
|
20
|
-
this.eventBus = eventBus
|
|
15
|
+
this.eventBus = eventBus ?? EventBus;
|
|
21
16
|
this.accomplishmentController = new AccomplishmentController(pluginId, this.eventBus);
|
|
22
17
|
// Listen for session token broadcasts from rimori-main (ExerciseSessionManager).
|
|
23
18
|
// When an exercise starts: adopt the exercise token unconditionally.
|
|
@@ -33,7 +28,6 @@ export class EventModule {
|
|
|
33
28
|
});
|
|
34
29
|
}
|
|
35
30
|
getGlobalEventTopic(preliminaryTopic) {
|
|
36
|
-
var _a;
|
|
37
31
|
if (preliminaryTopic.startsWith('global.')) {
|
|
38
32
|
return preliminaryTopic;
|
|
39
33
|
}
|
|
@@ -50,7 +44,7 @@ export class EventModule {
|
|
|
50
44
|
else if (topicParts.length > 3) {
|
|
51
45
|
throw new Error(`The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`);
|
|
52
46
|
}
|
|
53
|
-
const topicRoot =
|
|
47
|
+
const topicRoot = this.pluginId ?? 'global';
|
|
54
48
|
return `${topicRoot}.${preliminaryTopic}`;
|
|
55
49
|
}
|
|
56
50
|
/**
|
|
@@ -73,13 +67,10 @@ export class EventModule {
|
|
|
73
67
|
* @param data The data to request.
|
|
74
68
|
* @returns The response from the event.
|
|
75
69
|
*/
|
|
76
|
-
request(topic, data) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const sessionToken = (_a = this.aiModule.session.get()) !== null && _a !== void 0 ? _a : undefined;
|
|
81
|
-
return this.eventBus.request(this.pluginId, globalTopic, data, sessionToken);
|
|
82
|
-
});
|
|
70
|
+
async request(topic, data) {
|
|
71
|
+
const globalTopic = this.getGlobalEventTopic(topic);
|
|
72
|
+
const sessionToken = this.aiModule.session.get() ?? undefined;
|
|
73
|
+
return this.eventBus.request(this.pluginId, globalTopic, data, sessionToken);
|
|
83
74
|
}
|
|
84
75
|
/**
|
|
85
76
|
* Subscribe to an event.
|
|
@@ -113,14 +104,14 @@ export class EventModule {
|
|
|
113
104
|
const topics = Array.isArray(topic) ? topic : [topic];
|
|
114
105
|
let wrappedData = data;
|
|
115
106
|
if (typeof data === 'function') {
|
|
116
|
-
wrappedData = (event) =>
|
|
107
|
+
wrappedData = async (event) => {
|
|
117
108
|
const previousToken = this.aiModule.session.get();
|
|
118
109
|
if (event.ai_session_token) {
|
|
119
110
|
this.aiModule.session.set(event.ai_session_token);
|
|
120
111
|
}
|
|
121
112
|
try {
|
|
122
113
|
// console.log('responding to event', event);
|
|
123
|
-
return
|
|
114
|
+
return await data(event);
|
|
124
115
|
}
|
|
125
116
|
finally {
|
|
126
117
|
if (event.ai_session_token) {
|
|
@@ -132,7 +123,7 @@ export class EventModule {
|
|
|
132
123
|
}
|
|
133
124
|
}
|
|
134
125
|
}
|
|
135
|
-
}
|
|
126
|
+
};
|
|
136
127
|
}
|
|
137
128
|
this.eventBus.respond(this.pluginId, topics.map((t) => this.getGlobalEventTopic(t)), wrappedData);
|
|
138
129
|
}
|
|
@@ -140,10 +131,8 @@ export class EventModule {
|
|
|
140
131
|
* Emit an accomplishment.
|
|
141
132
|
* @param payload The payload to emit.
|
|
142
133
|
*/
|
|
143
|
-
emitAccomplishment(payload) {
|
|
144
|
-
|
|
145
|
-
this.accomplishmentController.emitAccomplishment(payload);
|
|
146
|
-
});
|
|
134
|
+
async emitAccomplishment(payload) {
|
|
135
|
+
this.accomplishmentController.emitAccomplishment(payload);
|
|
147
136
|
}
|
|
148
137
|
/**
|
|
149
138
|
* Subscribe to an accomplishment.
|
|
@@ -169,7 +158,7 @@ export class EventModule {
|
|
|
169
158
|
* @param params Optional additional parameters to pass to the action.
|
|
170
159
|
*/
|
|
171
160
|
emitMainPanelAction(pluginId, actionKey, params) {
|
|
172
|
-
this.emit('global.mainPanel.triggerAction',
|
|
161
|
+
this.emit('global.mainPanel.triggerAction', { plugin_id: pluginId, action_key: actionKey, ...params });
|
|
173
162
|
}
|
|
174
163
|
/**
|
|
175
164
|
* Subscribe to main panel actions triggered by the user from the dashboard.
|
|
@@ -196,15 +185,17 @@ export class EventModule {
|
|
|
196
185
|
*/
|
|
197
186
|
onMainPanelAction(callback, actionsToListen = []) {
|
|
198
187
|
const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
188
|
+
// Register the listener BEFORE emitting the request so that synchronous responses
|
|
189
|
+
// from the federated bridge (in-process, not postMessage) are captured.
|
|
190
|
+
// In iframe mode the bridge is async (postMessage) so order didn't matter before,
|
|
191
|
+
// but in federated mode the respond fires synchronously during emit.
|
|
192
|
+
const listener = this.on('action.requestMain', ({ data }) => {
|
|
204
193
|
if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
|
|
205
194
|
callback(data);
|
|
206
195
|
}
|
|
207
196
|
});
|
|
197
|
+
this.emit('action.requestMain');
|
|
198
|
+
return listener;
|
|
208
199
|
}
|
|
209
200
|
/**
|
|
210
201
|
* Subscribe to side panel actions triggered by the user from the dashboard.
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
/**
|
|
11
2
|
* Controller for exercise-related operations.
|
|
12
3
|
* Provides access to weekly exercises and exercise management.
|
|
13
4
|
*/
|
|
14
5
|
export class ExerciseModule {
|
|
6
|
+
supabase;
|
|
7
|
+
communicationHandler;
|
|
8
|
+
eventModule;
|
|
15
9
|
constructor(supabase, communicationHandler, _info, eventModule) {
|
|
16
10
|
this.supabase = supabase;
|
|
17
11
|
this.communicationHandler = communicationHandler;
|
|
@@ -22,14 +16,12 @@ export class ExerciseModule {
|
|
|
22
16
|
* Shows exercises for the current week that haven't expired.
|
|
23
17
|
* @returns Array of exercise objects.
|
|
24
18
|
*/
|
|
25
|
-
view() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return data || [];
|
|
32
|
-
});
|
|
19
|
+
async view() {
|
|
20
|
+
const { data, error } = await this.supabase.schema('public').from('weekly_exercises').select('*');
|
|
21
|
+
if (error) {
|
|
22
|
+
throw new Error(`Failed to fetch weekly exercises: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
return data || [];
|
|
33
25
|
}
|
|
34
26
|
/**
|
|
35
27
|
* Creates one or more exercises via the backend API.
|
|
@@ -38,21 +30,19 @@ export class ExerciseModule {
|
|
|
38
30
|
* @param params Exercise creation parameters (single or array).
|
|
39
31
|
* @returns Created exercise objects.
|
|
40
32
|
*/
|
|
41
|
-
add(params) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
body: JSON.stringify({ exercises }),
|
|
47
|
-
});
|
|
48
|
-
if (!response.ok) {
|
|
49
|
-
const errorText = yield response.text();
|
|
50
|
-
throw new Error(`Failed to create exercises: ${errorText}`);
|
|
51
|
-
}
|
|
52
|
-
const data = yield response.json();
|
|
53
|
-
this.eventModule.emit('global.exercises.triggerChange');
|
|
54
|
-
return data;
|
|
33
|
+
async add(params) {
|
|
34
|
+
const exercises = Array.isArray(params) ? params : [params];
|
|
35
|
+
const response = await this.communicationHandler.fetchBackend('/exercises', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
body: JSON.stringify({ exercises }),
|
|
55
38
|
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const errorText = await response.text();
|
|
41
|
+
throw new Error(`Failed to create exercises: ${errorText}`);
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
this.eventModule.emit('global.exercises.triggerChange');
|
|
45
|
+
return data;
|
|
56
46
|
}
|
|
57
47
|
/**
|
|
58
48
|
* Requests a new exercise session token from rimori-main.
|
|
@@ -66,22 +56,20 @@ export class ExerciseModule {
|
|
|
66
56
|
* @param params.actionKey The action key identifying this exercise type.
|
|
67
57
|
* @param params.knowledgeId Optional knowledge ID for tracking what was studied.
|
|
68
58
|
*/
|
|
69
|
-
start(params) {
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
async start(params) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const timeout = setTimeout(() => {
|
|
62
|
+
listener.off();
|
|
63
|
+
reject(new Error('Exercise start timed out: rimori-main did not respond within 5s'));
|
|
64
|
+
}, 5000);
|
|
65
|
+
const listener = this.eventModule.on('global.session.triggerUpdate', ({ data }) => {
|
|
66
|
+
if (data.session_token) {
|
|
67
|
+
clearTimeout(timeout);
|
|
73
68
|
listener.off();
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
const listener = this.eventModule.on('global.session.triggerUpdate', ({ data }) => {
|
|
77
|
-
if (data.session_token) {
|
|
78
|
-
clearTimeout(timeout);
|
|
79
|
-
listener.off();
|
|
80
|
-
resolve();
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
this.eventModule.emit('global.exercise.triggerStart', params);
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
84
71
|
});
|
|
72
|
+
this.eventModule.emit('global.exercise.triggerStart', params);
|
|
85
73
|
});
|
|
86
74
|
}
|
|
87
75
|
/**
|
|
@@ -89,17 +77,15 @@ export class ExerciseModule {
|
|
|
89
77
|
* @param id The exercise ID to delete.
|
|
90
78
|
* @returns Success status.
|
|
91
79
|
*/
|
|
92
|
-
delete(id) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
method: 'DELETE',
|
|
96
|
-
});
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
const errorText = yield response.text();
|
|
99
|
-
throw new Error(`Failed to delete exercise: ${errorText}`);
|
|
100
|
-
}
|
|
101
|
-
this.eventModule.emit('global.exercises.triggerChange');
|
|
102
|
-
return yield response.json();
|
|
80
|
+
async delete(id) {
|
|
81
|
+
const response = await this.communicationHandler.fetchBackend(`/exercises/${id}`, {
|
|
82
|
+
method: 'DELETE',
|
|
103
83
|
});
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const errorText = await response.text();
|
|
86
|
+
throw new Error(`Failed to delete exercise: ${errorText}`);
|
|
87
|
+
}
|
|
88
|
+
this.eventModule.emit('global.exercises.triggerChange');
|
|
89
|
+
return await response.json();
|
|
104
90
|
}
|
|
105
91
|
}
|
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { Translator } from '../../controller/TranslationController';
|
|
11
2
|
/**
|
|
12
3
|
* Controller for plugin-related operations.
|
|
13
4
|
* Provides access to plugin settings, user info, and plugin information.
|
|
14
5
|
*/
|
|
15
6
|
export class PluginModule {
|
|
7
|
+
pluginId;
|
|
8
|
+
rimoriInfo;
|
|
9
|
+
translator;
|
|
10
|
+
supabase;
|
|
11
|
+
communicationHandler;
|
|
12
|
+
/**
|
|
13
|
+
* The release channel of this plugin installation.
|
|
14
|
+
* Determines which database schema is used for plugin tables.
|
|
15
|
+
*/
|
|
16
|
+
releaseChannel;
|
|
17
|
+
applicationMode;
|
|
18
|
+
theme;
|
|
19
|
+
/**
|
|
20
|
+
* Whether text-to-speech is globally enabled (set in rimori-main navbar).
|
|
21
|
+
* Updated automatically when the user toggles TTS.
|
|
22
|
+
*/
|
|
23
|
+
ttsEnabled = true;
|
|
16
24
|
constructor(supabase, communicationHandler, info, ai) {
|
|
17
|
-
var _a;
|
|
18
|
-
/**
|
|
19
|
-
* Whether text-to-speech is globally enabled (set in rimori-main navbar).
|
|
20
|
-
* Updated automatically when the user toggles TTS.
|
|
21
|
-
*/
|
|
22
|
-
this.ttsEnabled = true;
|
|
23
25
|
this.rimoriInfo = info;
|
|
24
26
|
this.supabase = supabase;
|
|
25
27
|
this.pluginId = info.pluginId;
|
|
26
28
|
this.releaseChannel = info.releaseChannel;
|
|
27
29
|
this.communicationHandler = communicationHandler;
|
|
28
30
|
const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
|
|
29
|
-
this.translator = new Translator(info.interfaceLanguage,
|
|
30
|
-
this.ttsEnabled =
|
|
31
|
+
this.translator = new Translator(info.interfaceLanguage, currentPlugin?.endpoint || '', ai);
|
|
32
|
+
this.ttsEnabled = info.ttsEnabled ?? true;
|
|
31
33
|
this.communicationHandler.onUpdate((updatedInfo) => {
|
|
32
|
-
var _a;
|
|
33
34
|
this.rimoriInfo = updatedInfo;
|
|
34
|
-
this.ttsEnabled =
|
|
35
|
+
this.ttsEnabled = updatedInfo.ttsEnabled ?? true;
|
|
35
36
|
});
|
|
36
37
|
this.applicationMode = this.communicationHandler.getQueryParam('applicationMode');
|
|
37
38
|
this.theme = this.communicationHandler.getQueryParam('rm_theme') || 'light';
|
|
@@ -42,20 +43,17 @@ export class PluginModule {
|
|
|
42
43
|
* Otherwise, fetches user-specific settings.
|
|
43
44
|
* @returns The settings object or null if not found.
|
|
44
45
|
*/
|
|
45
|
-
fetchSettings() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.maybeSingle();
|
|
57
|
-
return (_a = data === null || data === void 0 ? void 0 : data.settings) !== null && _a !== void 0 ? _a : null;
|
|
58
|
-
});
|
|
46
|
+
async fetchSettings() {
|
|
47
|
+
const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
|
|
48
|
+
const { data } = await this.supabase
|
|
49
|
+
.schema('public')
|
|
50
|
+
.from('plugin_settings')
|
|
51
|
+
.select('*')
|
|
52
|
+
.eq('plugin_id', this.pluginId)
|
|
53
|
+
.eq('guild_id', this.rimoriInfo.guild.id)
|
|
54
|
+
.eq('is_guild_setting', isGuildSetting)
|
|
55
|
+
.maybeSingle();
|
|
56
|
+
return data?.settings ?? null;
|
|
59
57
|
}
|
|
60
58
|
/**
|
|
61
59
|
* Sets settings for the plugin.
|
|
@@ -64,82 +62,77 @@ export class PluginModule {
|
|
|
64
62
|
* @param settings - The settings object to save.
|
|
65
63
|
* @throws {Error} if RLS blocks the operation.
|
|
66
64
|
*/
|
|
67
|
-
setSettings(settings) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (updateError.code === '42501' || ((_a = updateError.message) === null || _a === void 0 ? void 0 : _a.includes('policy'))) {
|
|
93
|
-
throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
|
|
94
|
-
}
|
|
95
|
-
// proceed to try insert in case of other issues
|
|
96
|
-
}
|
|
97
|
-
if (updatedRows && updatedRows.length > 0) {
|
|
98
|
-
return; // updated successfully
|
|
65
|
+
async setSettings(settings) {
|
|
66
|
+
const isGuildSetting = !this.rimoriInfo.guild.allowUserPluginSettings;
|
|
67
|
+
const payload = {
|
|
68
|
+
plugin_id: this.pluginId,
|
|
69
|
+
settings,
|
|
70
|
+
guild_id: this.rimoriInfo.guild.id,
|
|
71
|
+
is_guild_setting: isGuildSetting,
|
|
72
|
+
};
|
|
73
|
+
if (isGuildSetting) {
|
|
74
|
+
payload.user_id = null;
|
|
75
|
+
}
|
|
76
|
+
// Try UPDATE first (safe with RLS). If nothing updated, INSERT.
|
|
77
|
+
const updateQuery = this.supabase
|
|
78
|
+
.schema('public')
|
|
79
|
+
.from('plugin_settings')
|
|
80
|
+
.update({ settings })
|
|
81
|
+
.eq('plugin_id', this.pluginId)
|
|
82
|
+
.eq('guild_id', this.rimoriInfo.guild.id)
|
|
83
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
84
|
+
const { data: updatedRows, error: updateError } = await (isGuildSetting
|
|
85
|
+
? updateQuery.is('user_id', null).select()
|
|
86
|
+
: updateQuery.select());
|
|
87
|
+
if (updateError) {
|
|
88
|
+
if (updateError.code === '42501' || updateError.message?.includes('policy')) {
|
|
89
|
+
throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
|
|
99
90
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
91
|
+
// proceed to try insert in case of other issues
|
|
92
|
+
}
|
|
93
|
+
if (updatedRows && updatedRows.length > 0) {
|
|
94
|
+
return; // updated successfully
|
|
95
|
+
}
|
|
96
|
+
// No row updated -> INSERT
|
|
97
|
+
const { error: insertError } = await this.supabase.schema('public').from('plugin_settings').insert(payload);
|
|
98
|
+
if (insertError) {
|
|
99
|
+
// In case of race condition (duplicate), try one more UPDATE
|
|
100
|
+
if (insertError.code === '23505' /* unique_violation */) {
|
|
101
|
+
const retry = this.supabase
|
|
102
|
+
.schema('public')
|
|
103
|
+
.from('plugin_settings')
|
|
104
|
+
.update({ settings })
|
|
105
|
+
.eq('plugin_id', this.pluginId)
|
|
106
|
+
.eq('guild_id', this.rimoriInfo.guild.id)
|
|
107
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
108
|
+
const { error: retryError } = await (isGuildSetting ? retry.is('user_id', null) : retry);
|
|
109
|
+
if (!retryError)
|
|
110
|
+
return;
|
|
117
111
|
}
|
|
118
|
-
|
|
112
|
+
throw insertError;
|
|
113
|
+
}
|
|
119
114
|
}
|
|
120
115
|
/**
|
|
121
116
|
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
122
117
|
* @param defaultSettings The default settings to use if no settings are found.
|
|
123
118
|
* @returns The settings for the plugin.
|
|
124
119
|
*/
|
|
125
|
-
getSettings(defaultSettings) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return storedSettings;
|
|
142
|
-
});
|
|
120
|
+
async getSettings(defaultSettings) {
|
|
121
|
+
const storedSettings = await this.fetchSettings();
|
|
122
|
+
if (!storedSettings) {
|
|
123
|
+
await this.setSettings(defaultSettings);
|
|
124
|
+
return defaultSettings;
|
|
125
|
+
}
|
|
126
|
+
// Handle settings migration
|
|
127
|
+
const storedKeys = Object.keys(storedSettings);
|
|
128
|
+
const defaultKeys = Object.keys(defaultSettings);
|
|
129
|
+
if (storedKeys.length !== defaultKeys.length) {
|
|
130
|
+
const validStoredSettings = Object.fromEntries(Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key)));
|
|
131
|
+
const mergedSettings = { ...defaultSettings, ...validStoredSettings };
|
|
132
|
+
await this.setSettings(mergedSettings);
|
|
133
|
+
return mergedSettings;
|
|
134
|
+
}
|
|
135
|
+
return storedSettings;
|
|
143
136
|
}
|
|
144
137
|
/**
|
|
145
138
|
* Get the current user info.
|
|
@@ -183,11 +176,9 @@ export class PluginModule {
|
|
|
183
176
|
* Get the translator for the plugin.
|
|
184
177
|
* @returns The translator for the plugin.
|
|
185
178
|
*/
|
|
186
|
-
getTranslator() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return this.translator;
|
|
190
|
-
});
|
|
179
|
+
async getTranslator() {
|
|
180
|
+
await this.translator.initialize();
|
|
181
|
+
return this.translator;
|
|
191
182
|
}
|
|
192
183
|
}
|
|
193
184
|
/** Ordered tiers from lowest to highest access level */
|