@ichinese/open-sdk 0.0.0-stage → 1.0.0-rc.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/dist/index.js ADDED
@@ -0,0 +1,820 @@
1
+ // src/errors.ts
2
+ var OpenApiError = class extends Error {
3
+ name = "OpenApiError";
4
+ /** HTTP status (0: no answer). */
5
+ status;
6
+ /** Stable error code: UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_FAILED, CONFLICT, RATE_LIMITED, … */
7
+ code;
8
+ requestId;
9
+ /** `details.reason`, e.g. `scope_denied`, `token_expired`, `code_used`. */
10
+ reason;
11
+ details;
12
+ /** Seconds to wait (429), from Retry-After. */
13
+ retryAfter;
14
+ constructor(init) {
15
+ super(init.message || init.code);
16
+ this.status = init.status;
17
+ this.code = init.code;
18
+ this.requestId = init.requestId ?? null;
19
+ this.details = init.details ?? {};
20
+ this.reason = typeof this.details.reason === "string" ? this.details.reason : null;
21
+ this.retryAfter = init.retryAfter ?? null;
22
+ }
23
+ /** The scope the user or institution refused (`scope_denied`): the app should do without it. */
24
+ get scope() {
25
+ return typeof this.details.scope === "string" ? this.details.scope : null;
26
+ }
27
+ /** The user chose「不再询问」for the refused scope: do not ask again. */
28
+ get neverAsk() {
29
+ return this.details.never_ask === true;
30
+ }
31
+ /** A refused, not yet asked or undeclared scope (403): degrade gracefully. */
32
+ get isScopeError() {
33
+ return this.status === 403 && ["scope_denied", "scope_pending", "scope_not_declared", "gold_only"].includes(String(this.reason));
34
+ }
35
+ };
36
+ var isOpenApiError = (e) => e instanceof OpenApiError;
37
+
38
+ // src/gen/meta.ts
39
+ var VERSION = "1.0.0-rc.2";
40
+ var DEFAULT_BASE_URL = "https://open.ichinese.cn";
41
+ var API_PREFIX = "/open-apis/v1";
42
+ var SCOPES = [
43
+ "openid",
44
+ "profile",
45
+ "org:member:readonly",
46
+ "org:member",
47
+ "class:readonly",
48
+ "class:member",
49
+ "schedule:readonly",
50
+ "schedule",
51
+ "attendance:readonly",
52
+ "coursework",
53
+ "grade:readonly",
54
+ "course",
55
+ "learning:progress:readonly",
56
+ "im:message:send_as_bot",
57
+ "im:chat:readonly",
58
+ "notify:send",
59
+ "live:recording:readonly",
60
+ "event",
61
+ "event:registration:readonly",
62
+ "job",
63
+ "job:candidate:readonly",
64
+ "trial",
65
+ "trade:order:readonly",
66
+ "review",
67
+ "profile:interest:readonly",
68
+ "profile:interest",
69
+ "profile:learning:readonly",
70
+ "report:create"
71
+ ];
72
+ var EVENT_TYPES = [
73
+ "app.installed",
74
+ "app.uninstalled",
75
+ "app.scopes_changed",
76
+ "coursework.submitted",
77
+ "grade.published",
78
+ "schedule.session_changed",
79
+ "event.registered",
80
+ "job.candidate_applied",
81
+ "trade.order_paid",
82
+ "review.created",
83
+ "trial.booked",
84
+ "im.message_received"
85
+ ];
86
+ var ERROR_REASONS = [
87
+ "invalid_credentials",
88
+ "invalid_token",
89
+ "token_expired",
90
+ "token_type",
91
+ "app_disabled",
92
+ "not_installed",
93
+ "not_available",
94
+ "scope_denied",
95
+ "scope_pending",
96
+ "scope_not_declared",
97
+ "gold_only",
98
+ "rate_limited",
99
+ "daily_quota_exceeded",
100
+ "code_expired",
101
+ "code_used",
102
+ "code_invalid",
103
+ "redirect_uri_mismatch",
104
+ "origin_not_trusted",
105
+ "ip_not_allowed"
106
+ ];
107
+ var OPERATION_TOKENS = {
108
+ getUserInfo: ["user"],
109
+ getTenant: ["tenant"],
110
+ listTenantMembers: ["tenant"],
111
+ inviteTenantMember: ["tenant"],
112
+ listClasses: ["tenant", "user"],
113
+ getClass: ["tenant", "user"],
114
+ listClassMembers: ["tenant", "user"],
115
+ addClassMember: ["tenant", "user"],
116
+ removeClassMember: ["tenant", "user"],
117
+ listClassSessions: ["tenant", "user"],
118
+ cancelClassSession: ["tenant", "user"],
119
+ rescheduleClassSession: ["tenant", "user"],
120
+ getSessionAttendance: ["tenant", "user"],
121
+ listAssignments: ["tenant", "user"],
122
+ createAssignment: ["tenant", "user"],
123
+ listSubmissions: ["tenant", "user"],
124
+ setSubmissionScore: ["tenant", "user"],
125
+ exportClassGrades: ["tenant", "user"],
126
+ listCourses: ["tenant", "user"],
127
+ getCourseOutline: ["tenant", "user"],
128
+ updateCourseOutline: ["tenant", "user"],
129
+ listCourseProgress: ["tenant", "user"],
130
+ sendBotMessage: ["tenant", "app"],
131
+ listBotChats: ["tenant", "app"],
132
+ sendNotification: ["tenant"],
133
+ listClassRecordings: ["tenant", "user"],
134
+ listTenantEvents: ["tenant"],
135
+ createTenantEvent: ["tenant"],
136
+ updateTenantEvent: ["tenant"],
137
+ listEventRegistrations: ["tenant"],
138
+ listTenantJobs: ["tenant"],
139
+ createTenantJob: ["tenant"],
140
+ listJobCandidates: ["tenant"],
141
+ listTrialVenues: ["tenant"],
142
+ listTrialSlots: ["tenant"],
143
+ createTrialSlots: ["tenant"],
144
+ cancelTrialSlot: ["tenant"],
145
+ listTrialBookings: ["tenant"],
146
+ confirmTrialBooking: ["tenant"],
147
+ listTradeOrders: ["tenant"],
148
+ listTenantReviews: ["tenant"],
149
+ replyReview: ["tenant"],
150
+ listPublicContent: ["app", "tenant", "user"],
151
+ getUserInterests: ["user"],
152
+ addInterestSignal: ["user"],
153
+ getUserLearningProfile: ["user"],
154
+ reportOnBehalf: ["user"]
155
+ };
156
+ var SCOPE_OPENID = "openid";
157
+ var SCOPE_PROFILE = "profile";
158
+ var SCOPE_ORG_MEMBER_READONLY = "org:member:readonly";
159
+ var SCOPE_ORG_MEMBER = "org:member";
160
+ var SCOPE_CLASS_READONLY = "class:readonly";
161
+ var SCOPE_CLASS_MEMBER = "class:member";
162
+ var SCOPE_SCHEDULE_READONLY = "schedule:readonly";
163
+ var SCOPE_SCHEDULE = "schedule";
164
+ var SCOPE_ATTENDANCE_READONLY = "attendance:readonly";
165
+ var SCOPE_COURSEWORK = "coursework";
166
+ var SCOPE_GRADE_READONLY = "grade:readonly";
167
+ var SCOPE_COURSE = "course";
168
+ var SCOPE_LEARNING_PROGRESS_READONLY = "learning:progress:readonly";
169
+ var SCOPE_IM_MESSAGE_SEND_AS_BOT = "im:message:send_as_bot";
170
+ var SCOPE_IM_CHAT_READONLY = "im:chat:readonly";
171
+ var SCOPE_NOTIFY_SEND = "notify:send";
172
+ var SCOPE_LIVE_RECORDING_READONLY = "live:recording:readonly";
173
+ var SCOPE_EVENT = "event";
174
+ var SCOPE_EVENT_REGISTRATION_READONLY = "event:registration:readonly";
175
+ var SCOPE_JOB = "job";
176
+ var SCOPE_JOB_CANDIDATE_READONLY = "job:candidate:readonly";
177
+ var SCOPE_TRIAL = "trial";
178
+ var SCOPE_TRADE_ORDER_READONLY = "trade:order:readonly";
179
+ var SCOPE_REVIEW = "review";
180
+ var SCOPE_PROFILE_INTEREST_READONLY = "profile:interest:readonly";
181
+ var SCOPE_PROFILE_INTEREST = "profile:interest";
182
+ var SCOPE_PROFILE_LEARNING_READONLY = "profile:learning:readonly";
183
+ var SCOPE_REPORT_CREATE = "report:create";
184
+
185
+ // src/gen/operations.ts
186
+ var Operations = class {
187
+ /** The current user (user token) (`GET /user/info`; user token; scopes: openid) */
188
+ getUserInfo(options) {
189
+ return this.call({ id: "getUserInfo", method: "GET", path: "/user/info", pathParams: {}, tokenTypes: ["user"] }, options);
190
+ }
191
+ /** The institution that installed the app (tenant token) (`GET /tenant`; tenant token) */
192
+ getTenant(options) {
193
+ return this.call({ id: "getTenant", method: "GET", path: "/tenant", pathParams: {}, tokenTypes: ["tenant"] }, options);
194
+ }
195
+ /** The institution's members (directory); members in all regions are listed together, without revealing regions (`GET /tenant/members`; tenant token; scopes: org:member:readonly, org:member) */
196
+ listTenantMembers(query, options) {
197
+ return this.call({ id: "listTenantMembers", method: "GET", path: "/tenant/members", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
198
+ }
199
+ /** Every item of `listTenantMembers`, page after page. */
200
+ listTenantMembersAll(query, options) {
201
+ return this.iterate({ id: "listTenantMembers", method: "GET", path: "/tenant/members", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
202
+ }
203
+ /** Invite a member (they join only after accepting, the same as inviting in the institution admin) (`POST /tenant/invitations`; tenant token; scopes: org:member) */
204
+ inviteTenantMember(body, options) {
205
+ return this.call({ id: "inviteTenantMember", method: "POST", path: "/tenant/invitations", pathParams: {}, body, tokenTypes: ["tenant"] }, options);
206
+ }
207
+ /** Classes: with a tenant token, all of the institution's classes; with a user token, the classes this user teaches (`GET /classes`; tenant / user token; scopes: class:readonly, class:member) */
208
+ listClasses(query, options) {
209
+ return this.call({ id: "listClasses", method: "GET", path: "/classes", pathParams: {}, query, tokenTypes: ["tenant", "user"] }, options);
210
+ }
211
+ /** Every item of `listClasses`, page after page. */
212
+ listClassesAll(query, options) {
213
+ return this.iterate({ id: "listClasses", method: "GET", path: "/classes", pathParams: {}, query, tokenTypes: ["tenant", "user"] }, options);
214
+ }
215
+ /** A class (`GET /classes/{class_id}`; tenant / user token; scopes: class:readonly, class:member) */
216
+ getClass(classId, options) {
217
+ return this.call({ id: "getClass", method: "GET", path: "/classes/{class_id}", pathParams: { class_id: classId }, tokenTypes: ["tenant", "user"] }, options);
218
+ }
219
+ /** Roster (teachers and learners) (`GET /classes/{class_id}/members`; tenant / user token; scopes: class:readonly, class:member) */
220
+ listClassMembers(classId, query, options) {
221
+ return this.call({ id: "listClassMembers", method: "GET", path: "/classes/{class_id}/members", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
222
+ }
223
+ /** Every item of `listClassMembers`, page after page. */
224
+ listClassMembersAll(classId, query, options) {
225
+ return this.iterate({ id: "listClassMembers", method: "GET", path: "/classes/{class_id}/members", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
226
+ }
227
+ /** Add one of the institution's members to a class (as a learner) (`POST /classes/{class_id}/members`; tenant / user token; scopes: class:member) */
228
+ addClassMember(classId, body, options) {
229
+ return this.call({ id: "addClassMember", method: "POST", path: "/classes/{class_id}/members", pathParams: { class_id: classId }, body, tokenTypes: ["tenant", "user"] }, options);
230
+ }
231
+ /** Remove a learner from a class (`DELETE /classes/{class_id}/members/{open_id}`; tenant / user token; scopes: class:member) */
232
+ removeClassMember(classId, openId, options) {
233
+ return this.call({ id: "removeClassMember", method: "DELETE", path: "/classes/{class_id}/members/{open_id}", pathParams: { class_id: classId, open_id: openId }, tokenTypes: ["tenant", "user"] }, options);
234
+ }
235
+ /** A class's sessions (within a time range of at most 100 days) (`GET /classes/{class_id}/sessions`; tenant / user token; scopes: schedule:readonly, schedule) */
236
+ listClassSessions(classId, query, options) {
237
+ return this.call({ id: "listClassSessions", method: "GET", path: "/classes/{class_id}/sessions", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
238
+ }
239
+ /** Every item of `listClassSessions`, page after page. */
240
+ listClassSessionsAll(classId, query, options) {
241
+ return this.iterate({ id: "listClassSessions", method: "GET", path: "/classes/{class_id}/sessions", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
242
+ }
243
+ /** Cancel a class session (members are notified, the same as cancelling in the timetable) (`POST /classes/{class_id}/sessions/{session_id}/cancel`; tenant / user token; scopes: schedule) */
244
+ cancelClassSession(classId, sessionId, options) {
245
+ return this.call({ id: "cancelClassSession", method: "POST", path: "/classes/{class_id}/sessions/{session_id}/cancel", pathParams: { class_id: classId, session_id: sessionId }, tokenTypes: ["tenant", "user"] }, options);
246
+ }
247
+ /** Reschedule a class session (`POST /classes/{class_id}/sessions/{session_id}/reschedule`; tenant / user token; scopes: schedule) */
248
+ rescheduleClassSession(classId, sessionId, body, options) {
249
+ return this.call({ id: "rescheduleClassSession", method: "POST", path: "/classes/{class_id}/sessions/{session_id}/reschedule", pathParams: { class_id: classId, session_id: sessionId }, body, tokenTypes: ["tenant", "user"] }, options);
250
+ }
251
+ /** Attendance of a class session (who came, and how long they stayed in the classroom) (`GET /classes/{class_id}/sessions/{session_id}/attendance`; tenant / user token; scopes: attendance:readonly) */
252
+ getSessionAttendance(classId, sessionId, options) {
253
+ return this.call({ id: "getSessionAttendance", method: "GET", path: "/classes/{class_id}/sessions/{session_id}/attendance", pathParams: { class_id: classId, session_id: sessionId }, tokenTypes: ["tenant", "user"] }, options);
254
+ }
255
+ /** A class's assignments and exams (`GET /classes/{class_id}/assignments`; tenant / user token; scopes: coursework, grade:readonly) */
256
+ listAssignments(classId, query, options) {
257
+ return this.call({ id: "listAssignments", method: "GET", path: "/classes/{class_id}/assignments", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
258
+ }
259
+ /** Every item of `listAssignments`, page after page. */
260
+ listAssignmentsAll(classId, query, options) {
261
+ return this.iterate({ id: "listAssignments", method: "GET", path: "/classes/{class_id}/assignments", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
262
+ }
263
+ /** Create an assignment (text instructions + due time; questions are edited in the platform). With a user token, the user must be a teacher of this class (`POST /classes/{class_id}/assignments`; tenant / user token; scopes: coursework) */
264
+ createAssignment(classId, body, options) {
265
+ return this.call({ id: "createAssignment", method: "POST", path: "/classes/{class_id}/assignments", pathParams: { class_id: classId }, body, tokenTypes: ["tenant", "user"] }, options);
266
+ }
267
+ /** An assignment's submissions and scores (`GET /classes/{class_id}/assignments/{assignment_id}/submissions`; tenant / user token; scopes: grade:readonly) */
268
+ listSubmissions(classId, assignmentId, query, options) {
269
+ return this.call({ id: "listSubmissions", method: "GET", path: "/classes/{class_id}/assignments/{assignment_id}/submissions", pathParams: { class_id: classId, assignment_id: assignmentId }, query, tokenTypes: ["tenant", "user"] }, options);
270
+ }
271
+ /** Every item of `listSubmissions`, page after page. */
272
+ listSubmissionsAll(classId, assignmentId, query, options) {
273
+ return this.iterate({ id: "listSubmissions", method: "GET", path: "/classes/{class_id}/assignments/{assignment_id}/submissions", pathParams: { class_id: classId, assignment_id: assignmentId }, query, tokenTypes: ["tenant", "user"] }, options);
274
+ }
275
+ /** Write back a score (the result of external grading); the learner is notified that grades are published (`PUT /classes/{class_id}/assignments/{assignment_id}/submissions/{open_id}/score`; tenant / user token; scopes: coursework) */
276
+ setSubmissionScore(classId, assignmentId, openId, body, options) {
277
+ return this.call({ id: "setSubmissionScore", method: "PUT", path: "/classes/{class_id}/assignments/{assignment_id}/submissions/{open_id}/score", pathParams: { class_id: classId, assignment_id: assignmentId, open_id: openId }, body, tokenTypes: ["tenant", "user"] }, options);
278
+ }
279
+ /** Export grades (one row per learner per assignment) (`GET /classes/{class_id}/grades`; tenant / user token; scopes: grade:readonly) */
280
+ exportClassGrades(classId, query, options) {
281
+ return this.call({ id: "exportClassGrades", method: "GET", path: "/classes/{class_id}/grades", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
282
+ }
283
+ /** Every item of `exportClassGrades`, page after page. */
284
+ exportClassGradesAll(classId, query, options) {
285
+ return this.iterate({ id: "exportClassGrades", method: "GET", path: "/classes/{class_id}/grades", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
286
+ }
287
+ /** The institution's recorded courses (tenant token) or the courses this user authored (user token) (`GET /courses`; tenant / user token; scopes: course, learning:progress:readonly) */
288
+ listCourses(query, options) {
289
+ return this.call({ id: "listCourses", method: "GET", path: "/courses", pathParams: {}, query, tokenTypes: ["tenant", "user"] }, options);
290
+ }
291
+ /** Every item of `listCourses`, page after page. */
292
+ listCoursesAll(query, options) {
293
+ return this.iterate({ id: "listCourses", method: "GET", path: "/courses", pathParams: {}, query, tokenTypes: ["tenant", "user"] }, options);
294
+ }
295
+ /** Course outline (titles and order of chapters and lessons) (`GET /courses/{course_id}`; tenant / user token; scopes: course) */
296
+ getCourseOutline(courseId, options) {
297
+ return this.call({ id: "getCourseOutline", method: "GET", path: "/courses/{course_id}", pathParams: { course_id: courseId }, tokenTypes: ["tenant", "user"] }, options);
298
+ }
299
+ /** Change a course's title and introduction; a published course that is changed goes through review again as usual (`PATCH /courses/{course_id}`; tenant / user token; scopes: course) */
300
+ updateCourseOutline(courseId, body, options) {
301
+ return this.call({ id: "updateCourseOutline", method: "PATCH", path: "/courses/{course_id}", pathParams: { course_id: courseId }, body, tokenTypes: ["tenant", "user"] }, options);
302
+ }
303
+ /** Learners' learning progress (number of lessons completed). A user token can only see its own (`GET /courses/{course_id}/progress`; tenant / user token; scopes: learning:progress:readonly) */
304
+ listCourseProgress(courseId, query, options) {
305
+ return this.call({ id: "listCourseProgress", method: "GET", path: "/courses/{course_id}/progress", pathParams: { course_id: courseId }, query, tokenTypes: ["tenant", "user"] }, options);
306
+ }
307
+ /** Every item of `listCourseProgress`, page after page. */
308
+ listCourseProgressAll(courseId, query, options) {
309
+ return this.iterate({ id: "listCourseProgress", method: "GET", path: "/courses/{course_id}/progress", pathParams: { course_id: courseId }, query, tokenTypes: ["tenant", "user"] }, options);
310
+ }
311
+ /** Send a bot message (text or card) to a group chat the bot is in, or privately to a user who installed the app (`POST /im/messages`; tenant / app token; scopes: im:message:send_as_bot) */
312
+ sendBotMessage(body, options) {
313
+ return this.call({ id: "sendBotMessage", method: "POST", path: "/im/messages", pathParams: {}, body, tokenTypes: ["tenant", "app"] }, options);
314
+ }
315
+ /** Group chats the bot is in (`GET /im/chats`; tenant / app token; scopes: im:chat:readonly) */
316
+ listBotChats(query, options) {
317
+ return this.call({ id: "listBotChats", method: "GET", path: "/im/chats", pathParams: {}, query, tokenTypes: ["tenant", "app"] }, options);
318
+ }
319
+ /** Every item of `listBotChats`, page after page. */
320
+ listBotChatsAll(query, options) {
321
+ return this.iterate({ id: "listBotChats", method: "GET", path: "/im/chats", pathParams: {}, query, tokenTypes: ["tenant", "app"] }, options);
322
+ }
323
+ /** Send a notification to members of this institution (it goes to "System notifications" and is pushed according to each recipient's notification settings and quiet hours) (`POST /notifications`; tenant token; scopes: notify:send) */
324
+ sendNotification(body, options) {
325
+ return this.call({ id: "sendNotification", method: "POST", path: "/notifications", pathParams: {}, body, tokenTypes: ["tenant"] }, options);
326
+ }
327
+ /** Replays and post-class summaries of a class's live sessions (no playback URL; `watch_url` opens the replay page in the platform) (`GET /classes/{class_id}/recordings`; tenant / user token; scopes: live:recording:readonly) */
328
+ listClassRecordings(classId, query, options) {
329
+ return this.call({ id: "listClassRecordings", method: "GET", path: "/classes/{class_id}/recordings", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
330
+ }
331
+ /** Every item of `listClassRecordings`, page after page. */
332
+ listClassRecordingsAll(classId, query, options) {
333
+ return this.iterate({ id: "listClassRecordings", method: "GET", path: "/classes/{class_id}/recordings", pathParams: { class_id: classId }, query, tokenTypes: ["tenant", "user"] }, options);
334
+ }
335
+ /** The institution's events (including drafts and those pending review) (`GET /events`; tenant token; scopes: event, event:registration:readonly) */
336
+ listTenantEvents(query, options) {
337
+ return this.call({ id: "listTenantEvents", method: "GET", path: "/events", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
338
+ }
339
+ /** Every item of `listTenantEvents`, page after page. */
340
+ listTenantEventsAll(query, options) {
341
+ return this.iterate({ id: "listTenantEvents", method: "GET", path: "/events", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
342
+ }
343
+ /** Create an event (a draft; the institution publishes it in its console, moderated as usual) (`POST /events`; tenant token; scopes: event) */
344
+ createTenantEvent(body, options) {
345
+ return this.call({ id: "createTenantEvent", method: "POST", path: "/events", pathParams: {}, body, tokenTypes: ["tenant"] }, options);
346
+ }
347
+ /** Change an event (the same fields as event editing in the platform's institution console) (`PATCH /events/{event_id}`; tenant token; scopes: event) */
348
+ updateTenantEvent(eventId, body, options) {
349
+ return this.call({ id: "updateTenantEvent", method: "PATCH", path: "/events/{event_id}", pathParams: { event_id: eventId }, body, tokenTypes: ["tenant"] }, options);
350
+ }
351
+ /** An event's registrations and check-ins (`GET /events/{event_id}/registrations`; tenant token; scopes: event:registration:readonly) */
352
+ listEventRegistrations(eventId, query, options) {
353
+ return this.call({ id: "listEventRegistrations", method: "GET", path: "/events/{event_id}/registrations", pathParams: { event_id: eventId }, query, tokenTypes: ["tenant"] }, options);
354
+ }
355
+ /** Every item of `listEventRegistrations`, page after page. */
356
+ listEventRegistrationsAll(eventId, query, options) {
357
+ return this.iterate({ id: "listEventRegistrations", method: "GET", path: "/events/{event_id}/registrations", pathParams: { event_id: eventId }, query, tokenTypes: ["tenant"] }, options);
358
+ }
359
+ /** The institution's job postings (including drafts and those pending review) (`GET /jobs`; tenant token; scopes: job, job:candidate:readonly) */
360
+ listTenantJobs(query, options) {
361
+ return this.call({ id: "listTenantJobs", method: "GET", path: "/jobs", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
362
+ }
363
+ /** Every item of `listTenantJobs`, page after page. */
364
+ listTenantJobsAll(query, options) {
365
+ return this.iterate({ id: "listTenantJobs", method: "GET", path: "/jobs", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
366
+ }
367
+ /** Sync in a job posting (as a draft; submitting and publishing still go through the institution's review and NetEase Yidun automatic review) (`POST /jobs`; tenant token; scopes: job) */
368
+ createTenantJob(body, options) {
369
+ return this.call({ id: "createTenantJob", method: "POST", path: "/jobs", pathParams: {}, body, tokenTypes: ["tenant"] }, options);
370
+ }
371
+ /** A job's candidates (the fields they agreed to give the institution when applying) (`GET /jobs/{job_id}/candidates`; tenant token; scopes: job:candidate:readonly) */
372
+ listJobCandidates(jobId, query, options) {
373
+ return this.call({ id: "listJobCandidates", method: "GET", path: "/jobs/{job_id}/candidates", pathParams: { job_id: jobId }, query, tokenTypes: ["tenant"] }, options);
374
+ }
375
+ /** Every item of `listJobCandidates`, page after page. */
376
+ listJobCandidatesAll(jobId, query, options) {
377
+ return this.iterate({ id: "listJobCandidates", method: "GET", path: "/jobs/{job_id}/candidates", pathParams: { job_id: jobId }, query, tokenTypes: ["tenant"] }, options);
378
+ }
379
+ /** The institution's teaching sites (use their venue_id to schedule trial slots) (`GET /trial/venues`; tenant token; scopes: trial) */
380
+ listTrialVenues(query, options) {
381
+ return this.call({ id: "listTrialVenues", method: "GET", path: "/trial/venues", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
382
+ }
383
+ /** Every item of `listTrialVenues`, page after page. */
384
+ listTrialVenuesAll(query, options) {
385
+ return this.iterate({ id: "listTrialVenues", method: "GET", path: "/trial/venues", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
386
+ }
387
+ /** Trial lesson slots (by default only those not over yet, by time) (`GET /trial/slots`; tenant token; scopes: trial) */
388
+ listTrialSlots(query, options) {
389
+ return this.call({ id: "listTrialSlots", method: "GET", path: "/trial/slots", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
390
+ }
391
+ /** Every item of `listTrialSlots`, page after page. */
392
+ listTrialSlotsAll(query, options) {
393
+ return this.iterate({ id: "listTrialSlots", method: "GET", path: "/trial/slots", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
394
+ }
395
+ /** Schedule trial lesson slots (they can repeat weekly); the teaching site must be published (`POST /trial/slots`; tenant token; scopes: trial) */
396
+ createTrialSlots(body, options) {
397
+ return this.call({ id: "createTrialSlots", method: "POST", path: "/trial/slots", pathParams: {}, body, tokenTypes: ["tenant"] }, options);
398
+ }
399
+ /** Cancel a trial lesson slot (whoever booked it is notified as usual; the booking is cancelled with it) (`POST /trial/slots/{slot_id}/cancel`; tenant token; scopes: trial) */
400
+ cancelTrialSlot(slotId, body, options) {
401
+ return this.call({ id: "cancelTrialSlot", method: "POST", path: "/trial/slots/{slot_id}/cancel", pathParams: { slot_id: slotId }, body, tokenTypes: ["tenant"] }, options);
402
+ }
403
+ /** Trial lesson bookings at the institution's teaching sites (`GET /trial/bookings`; tenant token; scopes: trial) */
404
+ listTrialBookings(query, options) {
405
+ return this.call({ id: "listTrialBookings", method: "GET", path: "/trial/bookings", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
406
+ }
407
+ /** Every item of `listTrialBookings`, page after page. */
408
+ listTrialBookingsAll(query, options) {
409
+ return this.iterate({ id: "listTrialBookings", method: "GET", path: "/trial/bookings", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
410
+ }
411
+ /** Confirm a trial lesson booking (`POST /trial/bookings/{booking_id}/confirm`; tenant token; scopes: trial) */
412
+ confirmTrialBooking(bookingId, options) {
413
+ return this.call({ id: "confirmTrialBooking", method: "POST", path: "/trial/bookings/{booking_id}/confirm", pathParams: { booking_id: bookingId }, tokenTypes: ["tenant"] }, options);
414
+ }
415
+ /** The institution's orders (read-only, for reconciliation). Write operations involving money are not open (`GET /trade/orders`; tenant token; scopes: trade:order:readonly) */
416
+ listTradeOrders(query, options) {
417
+ return this.call({ id: "listTradeOrders", method: "GET", path: "/trade/orders", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
418
+ }
419
+ /** Every item of `listTradeOrders`, page after page. */
420
+ listTradeOrdersAll(query, options) {
421
+ return this.iterate({ id: "listTradeOrders", method: "GET", path: "/trade/orders", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
422
+ }
423
+ /** Reviews of this institution (and its courses and teaching sites) (`GET /reviews`; tenant token; scopes: review) */
424
+ listTenantReviews(query, options) {
425
+ return this.call({ id: "listTenantReviews", method: "GET", path: "/reviews", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
426
+ }
427
+ /** Every item of `listTenantReviews`, page after page. */
428
+ listTenantReviewsAll(query, options) {
429
+ return this.iterate({ id: "listTenantReviews", method: "GET", path: "/reviews", pathParams: {}, query, tokenTypes: ["tenant"] }, options);
430
+ }
431
+ /** Reply to a review on behalf of the institution (one reply per review; the content is moderated as usual) (`POST /reviews/{review_id}/reply`; tenant token; scopes: review) */
432
+ replyReview(reviewId, body, options) {
433
+ return this.call({ id: "replyReview", method: "POST", path: "/reviews/{review_id}/reply", pathParams: { review_id: reviewId }, body, tokenTypes: ["tenant"] }, options);
434
+ }
435
+ /** Public courses, events and jobs shown in this region (`GET /public/{kind}`; app / tenant / user token) */
436
+ listPublicContent(kind, query, options) {
437
+ return this.call({ id: "listPublicContent", method: "GET", path: "/public/{kind}", pathParams: { kind }, query, tokenTypes: ["app", "tenant", "user"] }, options);
438
+ }
439
+ /** Every item of `listPublicContent`, page after page. */
440
+ listPublicContentAll(kind, query, options) {
441
+ return this.iterate({ id: "listPublicContent", method: "GET", path: "/public/{kind}", pathParams: { kind }, query, tokenTypes: ["app", "tenant", "user"] }, options);
442
+ }
443
+ /** The user's interest profile (weighted tags of topics and content types; raw browsing and interaction records are not given) (`GET /user/profile/interests`; user token; scopes: profile:interest:readonly) */
444
+ getUserInterests(options) {
445
+ return this.call({ id: "getUserInterests", method: "GET", path: "/user/profile/interests", pathParams: {}, tokenTypes: ["user"] }, options);
446
+ }
447
+ /** Add an interest signal with its source (`POST /user/profile/interest_signals`; user token; scopes: profile:interest) */
448
+ addInterestSignal(body, options) {
449
+ return this.call({ id: "addInterestSignal", method: "POST", path: "/user/profile/interest_signals", pathParams: {}, body, tokenTypes: ["user"] }, options);
450
+ }
451
+ /** The user's learning profile (level, goals, languages being learned) (`GET /user/profile/learning`; user token; scopes: profile:learning:readonly) */
452
+ getUserLearningProfile(options) {
453
+ return this.call({ id: "getUserLearningProfile", method: "GET", path: "/user/profile/learning", pathParams: {}, tokenTypes: ["user"] }, options);
454
+ }
455
+ /** Report a piece of public content on behalf of a user (it enters the platform's moderation queue; the user can see it in their own report history) (`POST /reports`; user token; scopes: report:create) */
456
+ reportOnBehalf(body, options) {
457
+ return this.call({ id: "reportOnBehalf", method: "POST", path: "/reports", pathParams: {}, body, tokenTypes: ["user"] }, options);
458
+ }
459
+ };
460
+
461
+ // src/client.ts
462
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
463
+ var IDEMPOTENT = /* @__PURE__ */ new Set(["GET", "PUT", "DELETE"]);
464
+ var Client = class extends Operations {
465
+ appId;
466
+ baseUrl;
467
+ appSecret;
468
+ maxRetries;
469
+ timeoutMs;
470
+ fetchFn;
471
+ refreshBefore;
472
+ userAgent;
473
+ /** Base of the retry backoff in ms (tests make it small). */
474
+ retryBaseMs = 500;
475
+ tokens = /* @__PURE__ */ new Map();
476
+ inflight = /* @__PURE__ */ new Map();
477
+ constructor(options) {
478
+ super();
479
+ if (!options.appId || !options.appSecret) throw new TypeError("appId and appSecret are required");
480
+ this.appId = options.appId;
481
+ this.appSecret = options.appSecret;
482
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "").replace(/\/open-apis\/v1$/, "");
483
+ this.maxRetries = options.maxRetries ?? 3;
484
+ this.timeoutMs = options.timeoutMs ?? 3e4;
485
+ this.fetchFn = options.fetch ?? fetch;
486
+ this.refreshBefore = options.refreshBeforeSeconds ?? 300;
487
+ this.userAgent = `ichinese-open-sdk-node/${VERSION}${options.userAgent ? ` ${options.userAgent}` : ""}`;
488
+ }
489
+ // ── tokens ────────────────────────────────────────────────────────────────
490
+ /** The app token (cached, renewed before it expires). */
491
+ async appAccessToken() {
492
+ return (await this.cached("app", async () => {
493
+ const r = await this.send("POST", "/auth/app_access_token", {
494
+ body: this.credentials()
495
+ });
496
+ return { token: r.app_access_token, expiresIn: r.expires_in };
497
+ })).token;
498
+ }
499
+ /** The tenant token of an institution that installed the app (cached per institution, renewed). */
500
+ async tenantAccessToken(tenantKey) {
501
+ return (await this.cached(`tenant:${tenantKey}`, async () => {
502
+ const r = await this.send("POST", "/auth/tenant_access_token", {
503
+ body: { ...this.credentials(), tenant_key: tenantKey }
504
+ });
505
+ return { token: r.tenant_access_token, expiresIn: r.expires_in };
506
+ })).token;
507
+ }
508
+ /**
509
+ * The web login (免登): exchanges the code your page got from the JS SDK's `requestAuthCode()` for a
510
+ * user token and the user's `open_id`. A code from the plain-browser fallback (standard OIDC) also
511
+ * needs the `redirectUri` and `codeVerifier` the JS SDK returned with it. Exchange codes on your
512
+ * backend only: the App Secret never goes to the page.
513
+ */
514
+ exchangeAuthCode(code, opts = {}) {
515
+ return this.send("POST", "/auth/exchange_auth_code", {
516
+ body: {
517
+ ...this.credentials(),
518
+ code,
519
+ ...opts.redirectUri ? { redirect_uri: opts.redirectUri } : {},
520
+ ...opts.codeVerifier ? { code_verifier: opts.codeVerifier } : {}
521
+ },
522
+ retryPost: false
523
+ });
524
+ }
525
+ /** A new pair for a refresh token (the old one stops working). */
526
+ refreshUserToken(refreshToken) {
527
+ return this.send("POST", "/auth/refresh_user_token", {
528
+ body: { ...this.credentials(), refresh_token: refreshToken },
529
+ retryPost: false
530
+ });
531
+ }
532
+ /** Revokes a user or refresh token (when the user signs out of your app). */
533
+ async revokeToken(token) {
534
+ await this.send("POST", "/auth/revoke", { body: { ...this.credentials(), token } });
535
+ }
536
+ /** Forgets the cached app and tenant tokens (they are fetched again when next needed). */
537
+ clearTokenCache() {
538
+ this.tokens.clear();
539
+ }
540
+ // ── calls ─────────────────────────────────────────────────────────────────
541
+ /** Any open API call by method and path (`/tenant/members`), for what the generated methods do not cover. */
542
+ request(method, path, init = {}, options) {
543
+ return this.call(
544
+ {
545
+ id: path,
546
+ method,
547
+ path,
548
+ pathParams: {},
549
+ query: init.query,
550
+ body: init.body,
551
+ tokenTypes: init.tokenTypes ?? ["app", "tenant", "user"]
552
+ },
553
+ options
554
+ );
555
+ }
556
+ async call(spec, options = {}) {
557
+ const path = spec.path.replace(/\{(\w+)\}/g, (_, name) => {
558
+ const v = spec.pathParams[name];
559
+ if (v === void 0 || v === null || v === "") throw new TypeError(`${spec.id}: ${name} is required`);
560
+ return encodeURIComponent(String(v));
561
+ });
562
+ return this.send(spec.method, path, { query: spec.query, body: spec.body, auth: { spec, options } });
563
+ }
564
+ async *iterate(spec, options = {}) {
565
+ let pageToken;
566
+ const query = { ...spec.query ?? {} };
567
+ if (query.page_size === void 0) query.page_size = 100;
568
+ do {
569
+ const page = await this.call(
570
+ { ...spec, query: { ...query, ...pageToken ? { page_token: pageToken } : {} } },
571
+ options
572
+ );
573
+ for (const item of page.items ?? []) yield item;
574
+ pageToken = page.has_more ? page.page_token : null;
575
+ } while (pageToken);
576
+ }
577
+ // ── internals ─────────────────────────────────────────────────────────────
578
+ credentials() {
579
+ return { app_id: this.appId, app_secret: this.appSecret };
580
+ }
581
+ async cached(key, fetchToken) {
582
+ const hit = this.tokens.get(key);
583
+ if (hit && hit.expiresAt - this.refreshBefore * 1e3 > Date.now()) return hit;
584
+ const running = this.inflight.get(key);
585
+ if (running) return running;
586
+ const p = fetchToken().then((r) => {
587
+ const c = { token: r.token, expiresAt: Date.now() + r.expiresIn * 1e3 };
588
+ this.tokens.set(key, c);
589
+ return c;
590
+ }).finally(() => this.inflight.delete(key));
591
+ this.inflight.set(key, p);
592
+ return p;
593
+ }
594
+ /** The token for a call, and the cache key it came from (dropped and fetched again after a 401). */
595
+ async tokenFor(spec, o) {
596
+ if (o.accessToken) return { token: o.accessToken };
597
+ if (o.userAccessToken) return { token: o.userAccessToken };
598
+ if (o.tenantKey) return { token: await this.tenantAccessToken(o.tenantKey), cacheKey: `tenant:${o.tenantKey}` };
599
+ if (spec.tokenTypes.includes("app")) return { token: await this.appAccessToken(), cacheKey: "app" };
600
+ throw new OpenApiError({
601
+ status: 0,
602
+ code: "TOKEN_REQUIRED",
603
+ message: `${spec.id} takes a ${spec.tokenTypes.join(" or ")} token: pass { tenantKey } or { userAccessToken }`
604
+ });
605
+ }
606
+ url(path, query) {
607
+ const u = new URL(`${this.baseUrl}${API_PREFIX}${path}`);
608
+ for (const [k, v] of Object.entries(query ?? {})) {
609
+ if (v === void 0 || v === null || v === "") continue;
610
+ if (Array.isArray(v)) for (const x of v) u.searchParams.append(k, String(x));
611
+ else u.searchParams.set(k, String(v));
612
+ }
613
+ return u.toString();
614
+ }
615
+ sleep(ms, signal) {
616
+ return new Promise((resolve, reject) => {
617
+ const t = setTimeout(resolve, ms);
618
+ signal?.addEventListener(
619
+ "abort",
620
+ () => {
621
+ clearTimeout(t);
622
+ reject(signal.reason);
623
+ },
624
+ { once: true }
625
+ );
626
+ });
627
+ }
628
+ backoff(attempt) {
629
+ const base = this.retryBaseMs * 2 ** attempt;
630
+ return Math.min(3e4, base + Math.random() * base * 0.25);
631
+ }
632
+ async send(method, path, init) {
633
+ const options = init.auth?.options ?? {};
634
+ let refreshed = false;
635
+ for (let attempt = 0; ; attempt++) {
636
+ const headers = {
637
+ accept: "application/json",
638
+ "user-agent": this.userAgent,
639
+ ...options.headers
640
+ };
641
+ let cacheKey;
642
+ if (init.auth) {
643
+ const t = await this.tokenFor(init.auth.spec, options);
644
+ headers.authorization = `Bearer ${t.token}`;
645
+ cacheKey = t.cacheKey;
646
+ }
647
+ if (init.body !== void 0) headers["content-type"] = "application/json";
648
+ const timeout = AbortSignal.timeout(this.timeoutMs);
649
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
650
+ const canRetry = attempt < this.maxRetries;
651
+ const idempotent = IDEMPOTENT.has(method) || method === "POST" && init.retryPost !== false && !init.auth;
652
+ let res;
653
+ try {
654
+ res = await this.fetchFn(this.url(path, init.query), {
655
+ method,
656
+ headers,
657
+ body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
658
+ signal
659
+ });
660
+ } catch (err) {
661
+ if (options.signal?.aborted) throw err;
662
+ const timedOut = timeout.aborted;
663
+ if (canRetry && idempotent) {
664
+ await this.sleep(this.backoff(attempt), options.signal);
665
+ continue;
666
+ }
667
+ throw new OpenApiError({
668
+ status: 0,
669
+ code: timedOut ? "TIMEOUT" : "NETWORK",
670
+ message: `${method} ${path}: ${err.message}`
671
+ });
672
+ }
673
+ if (res.ok) {
674
+ if (res.status === 204) return void 0;
675
+ const text = await res.text();
676
+ return text ? JSON.parse(text) : void 0;
677
+ }
678
+ const error = await toError(res);
679
+ if (res.status === 429 && canRetry) {
680
+ const reset = Number(res.headers.get("x-ratelimit-reset"));
681
+ const wait = error.retryAfter ?? (Number.isFinite(reset) && reset > 0 ? reset : null);
682
+ if (error.reason !== "daily_quota_exceeded") {
683
+ await this.sleep(wait !== null ? wait * 1e3 : this.backoff(attempt), options.signal);
684
+ continue;
685
+ }
686
+ }
687
+ if (RETRY_STATUSES.has(res.status) && canRetry && idempotent) {
688
+ await this.sleep(this.backoff(attempt), options.signal);
689
+ continue;
690
+ }
691
+ if (res.status === 401 && cacheKey && !refreshed && (error.reason === "token_expired" || error.reason === "invalid_token")) {
692
+ this.tokens.delete(cacheKey);
693
+ refreshed = true;
694
+ attempt--;
695
+ continue;
696
+ }
697
+ throw error;
698
+ }
699
+ }
700
+ };
701
+ async function toError(res) {
702
+ let body = {};
703
+ try {
704
+ body = await res.json();
705
+ } catch {
706
+ body = {};
707
+ }
708
+ const retryAfter = Number(res.headers.get("retry-after"));
709
+ return new OpenApiError({
710
+ status: res.status,
711
+ code: body.error?.code ?? `HTTP_${res.status}`,
712
+ message: body.error?.message ?? res.statusText,
713
+ requestId: body.error?.request_id ?? res.headers.get("x-request-id"),
714
+ details: body.error?.details ?? {},
715
+ retryAfter: Number.isFinite(retryAfter) && res.headers.has("retry-after") ? retryAfter : null
716
+ });
717
+ }
718
+
719
+ // src/events.ts
720
+ import { createDecipheriv, createHash, createHmac, timingSafeEqual } from "node:crypto";
721
+ var header = (h, name) => {
722
+ if (typeof h.get === "function") return h.get(name) ?? void 0;
723
+ const rec = h;
724
+ const v = rec[name] ?? rec[name.toLowerCase()] ?? Object.entries(rec).find(([k]) => k.toLowerCase() === name.toLowerCase())?.[1];
725
+ return Array.isArray(v) ? v[0] : v;
726
+ };
727
+ function sign(signingSecret, timestamp, nonce, body) {
728
+ return `v1=${createHmac("sha256", signingSecret).update(`${timestamp}.${nonce}.`).update(body).digest("hex")}`;
729
+ }
730
+ function verifySignature(headers, rawBody, signingSecret, opts = {}) {
731
+ const fail = (message) => new OpenApiError({ status: 401, code: "INVALID_SIGNATURE", message });
732
+ const timestamp = header(headers, "x-ich-timestamp");
733
+ const nonce = header(headers, "x-ich-nonce");
734
+ const signature = header(headers, "x-ich-signature");
735
+ if (!timestamp || !nonce || !signature) throw fail("missing X-Ich-Timestamp, X-Ich-Nonce or X-Ich-Signature");
736
+ const now = Math.floor((opts.now ?? Date.now()) / 1e3);
737
+ if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > (opts.toleranceSeconds ?? 300)) {
738
+ throw fail("the timestamp is too far from now");
739
+ }
740
+ const expected = Buffer.from(sign(signingSecret, timestamp, nonce, rawBody));
741
+ const given = Buffer.from(signature);
742
+ if (expected.length !== given.length || !timingSafeEqual(expected, given)) throw fail("the signature does not match");
743
+ }
744
+ function decrypt(encrypted, encryptKey) {
745
+ const data = Buffer.from(encrypted, "base64");
746
+ if (data.length < 12 + 16) throw new OpenApiError({ status: 400, code: "INVALID_PAYLOAD", message: "too short" });
747
+ const key = createHash("sha256").update(encryptKey).digest();
748
+ const iv = data.subarray(0, 12);
749
+ const tag = data.subarray(data.length - 16);
750
+ const d = createDecipheriv("aes-256-gcm", key, iv);
751
+ d.setAuthTag(tag);
752
+ try {
753
+ return Buffer.concat([d.update(data.subarray(12, data.length - 16)), d.final()]).toString("utf8");
754
+ } catch {
755
+ throw new OpenApiError({ status: 400, code: "INVALID_PAYLOAD", message: "cannot decrypt (wrong encrypt key?)" });
756
+ }
757
+ }
758
+ function parseEvent(headers, rawBody, opts) {
759
+ verifySignature(headers, rawBody, opts.signingSecret, opts);
760
+ let json = JSON.parse(typeof rawBody === "string" ? rawBody : rawBody.toString("utf8"));
761
+ if (typeof json.encrypt === "string") {
762
+ if (!opts.encryptKey) {
763
+ throw new OpenApiError({ status: 400, code: "INVALID_PAYLOAD", message: "encrypted push but no encryptKey" });
764
+ }
765
+ json = JSON.parse(decrypt(json.encrypt, opts.encryptKey));
766
+ }
767
+ return json;
768
+ }
769
+ function parseCardAction(headers, rawBody, opts) {
770
+ return parseEvent(headers, rawBody, opts);
771
+ }
772
+ var isUrlVerification = (x) => typeof x === "object" && x !== null && x.type === "url_verification";
773
+ var challengeResponse = (v) => ({ challenge: v.challenge });
774
+ export {
775
+ API_PREFIX,
776
+ Client,
777
+ DEFAULT_BASE_URL,
778
+ ERROR_REASONS,
779
+ EVENT_TYPES,
780
+ OPERATION_TOKENS,
781
+ OpenApiError,
782
+ SCOPES,
783
+ SCOPE_ATTENDANCE_READONLY,
784
+ SCOPE_CLASS_MEMBER,
785
+ SCOPE_CLASS_READONLY,
786
+ SCOPE_COURSE,
787
+ SCOPE_COURSEWORK,
788
+ SCOPE_EVENT,
789
+ SCOPE_EVENT_REGISTRATION_READONLY,
790
+ SCOPE_GRADE_READONLY,
791
+ SCOPE_IM_CHAT_READONLY,
792
+ SCOPE_IM_MESSAGE_SEND_AS_BOT,
793
+ SCOPE_JOB,
794
+ SCOPE_JOB_CANDIDATE_READONLY,
795
+ SCOPE_LEARNING_PROGRESS_READONLY,
796
+ SCOPE_LIVE_RECORDING_READONLY,
797
+ SCOPE_NOTIFY_SEND,
798
+ SCOPE_OPENID,
799
+ SCOPE_ORG_MEMBER,
800
+ SCOPE_ORG_MEMBER_READONLY,
801
+ SCOPE_PROFILE,
802
+ SCOPE_PROFILE_INTEREST,
803
+ SCOPE_PROFILE_INTEREST_READONLY,
804
+ SCOPE_PROFILE_LEARNING_READONLY,
805
+ SCOPE_REPORT_CREATE,
806
+ SCOPE_REVIEW,
807
+ SCOPE_SCHEDULE,
808
+ SCOPE_SCHEDULE_READONLY,
809
+ SCOPE_TRADE_ORDER_READONLY,
810
+ SCOPE_TRIAL,
811
+ VERSION,
812
+ challengeResponse,
813
+ decrypt,
814
+ isOpenApiError,
815
+ isUrlVerification,
816
+ parseCardAction,
817
+ parseEvent,
818
+ sign,
819
+ verifySignature
820
+ };