@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Center for Language Education and Cooperation (CLEC)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
- # Temporary Holding Version
1
+ # iChinese Open Platform SDK for Node.js
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Prerelease 1.0.0-rc.2.** 这是预发布版,开放平台还没有正式开放,SDK 的默认地址可能连不上。This is a prerelease: the open platform is not open to the public yet, so the SDK's default address may not answer.
4
+
5
+ 爱中文开放平台的 Node.js 服务端 SDK:开放接口(`/open-apis/v1`)的每个接口都是一个方法,并处理令牌、重试、分页和事件推送。
6
+
7
+ The server SDK of the iChinese open platform. Every operation of the open API is a method on `Client`
8
+ (generated from the contract), plus what each app would otherwise write itself: getting the app and
9
+ tenant tokens and renewing them before they expire, retrying after rate limits and server errors with
10
+ backoff, paging through lists, typed errors, exchanging the web login's codes, and verifying and
11
+ decrypting event pushes. No runtime dependencies.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @ichinese/open-sdk
17
+ ```
18
+
19
+ Requires Node.js 20 or newer. ESM and CommonJS are both supported, with type declarations.
20
+
21
+ ## Quick start
22
+
23
+ List an institution's members with a tenant token:
24
+
25
+ ```ts
26
+ import { Client, isOpenApiError } from '@ichinese/open-sdk';
27
+
28
+ const client = new Client({ appId: process.env.APP_ID!, appSecret: process.env.APP_SECRET! });
29
+
30
+ try {
31
+ const tenant = await client.getTenant({ tenantKey });
32
+ console.log(`${tenant.name} (${tenant.rating})`);
33
+ for await (const m of client.listTenantMembersAll({}, { tenantKey })) {
34
+ console.log(`${m.open_id} ${m.name} ${m.roles.join(',')}`);
35
+ }
36
+ } catch (e) {
37
+ // The institution's admin may have refused this scope.
38
+ if (isOpenApiError(e) && e.isScopeError) console.log(`not allowed (${e.reason})`);
39
+ else throw e;
40
+ }
41
+ ```
42
+
43
+ The `examples` directory has this one in full, a backend for the web login and a server for event pushes.
44
+
45
+ ## Documentation
46
+
47
+ Guides and the API reference: <https://open.ichinese.cn>
48
+
49
+ This repository is generated from the platform's monorepo at each release, so a change made here would be
50
+ overwritten; please open an issue instead.
51
+
52
+ ## License
53
+
54
+ MIT
@@ -0,0 +1,53 @@
1
+ import { Operations } from './gen/operations.ts';
2
+ import type { CallSpec, ClientOptions, RequestOptions, TokenType, UserAccessToken } from './types.ts';
3
+ export declare class Client extends Operations {
4
+ readonly appId: string;
5
+ readonly baseUrl: string;
6
+ private readonly appSecret;
7
+ private readonly maxRetries;
8
+ private readonly timeoutMs;
9
+ private readonly fetchFn;
10
+ private readonly refreshBefore;
11
+ private readonly userAgent;
12
+ /** Base of the retry backoff in ms (tests make it small). */
13
+ retryBaseMs: number;
14
+ private readonly tokens;
15
+ private readonly inflight;
16
+ constructor(options: ClientOptions);
17
+ /** The app token (cached, renewed before it expires). */
18
+ appAccessToken(): Promise<string>;
19
+ /** The tenant token of an institution that installed the app (cached per institution, renewed). */
20
+ tenantAccessToken(tenantKey: string): Promise<string>;
21
+ /**
22
+ * The web login (免登): exchanges the code your page got from the JS SDK's `requestAuthCode()` for a
23
+ * user token and the user's `open_id`. A code from the plain-browser fallback (standard OIDC) also
24
+ * needs the `redirectUri` and `codeVerifier` the JS SDK returned with it. Exchange codes on your
25
+ * backend only: the App Secret never goes to the page.
26
+ */
27
+ exchangeAuthCode(code: string, opts?: {
28
+ redirectUri?: string;
29
+ codeVerifier?: string;
30
+ }): Promise<UserAccessToken>;
31
+ /** A new pair for a refresh token (the old one stops working). */
32
+ refreshUserToken(refreshToken: string): Promise<UserAccessToken>;
33
+ /** Revokes a user or refresh token (when the user signs out of your app). */
34
+ revokeToken(token: string): Promise<void>;
35
+ /** Forgets the cached app and tenant tokens (they are fetched again when next needed). */
36
+ clearTokenCache(): void;
37
+ /** Any open API call by method and path (`/tenant/members`), for what the generated methods do not cover. */
38
+ request<T = unknown>(method: CallSpec['method'], path: string, init?: {
39
+ query?: Record<string, unknown>;
40
+ body?: unknown;
41
+ tokenTypes?: TokenType[];
42
+ }, options?: RequestOptions): Promise<T>;
43
+ protected call<T>(spec: CallSpec, options?: RequestOptions): Promise<T>;
44
+ protected iterate<T>(spec: CallSpec, options?: RequestOptions): AsyncIterable<T>;
45
+ private credentials;
46
+ private cached;
47
+ /** The token for a call, and the cache key it came from (dropped and fetched again after a 401). */
48
+ private tokenFor;
49
+ private url;
50
+ private sleep;
51
+ private backoff;
52
+ private send;
53
+ }
@@ -0,0 +1,33 @@
1
+ import type { ErrorReason } from './gen/meta.ts';
2
+ /**
3
+ * An error answer of the open API (`{"error": {"code", "message", "request_id", "details"}}`), or a
4
+ * failure before one came (`code` NETWORK / TIMEOUT, `status` 0).
5
+ */
6
+ export declare class OpenApiError extends Error {
7
+ readonly name = "OpenApiError";
8
+ /** HTTP status (0: no answer). */
9
+ readonly status: number;
10
+ /** Stable error code: UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_FAILED, CONFLICT, RATE_LIMITED, … */
11
+ readonly code: string;
12
+ readonly requestId: string | null;
13
+ /** `details.reason`, e.g. `scope_denied`, `token_expired`, `code_used`. */
14
+ readonly reason: ErrorReason | (string & {}) | null;
15
+ readonly details: Record<string, unknown>;
16
+ /** Seconds to wait (429), from Retry-After. */
17
+ readonly retryAfter: number | null;
18
+ constructor(init: {
19
+ status: number;
20
+ code: string;
21
+ message: string;
22
+ requestId?: string | null;
23
+ details?: Record<string, unknown>;
24
+ retryAfter?: number | null;
25
+ });
26
+ /** The scope the user or institution refused (`scope_denied`): the app should do without it. */
27
+ get scope(): string | null;
28
+ /** The user chose「不再询问」for the refused scope: do not ask again. */
29
+ get neverAsk(): boolean;
30
+ /** A refused, not yet asked or undeclared scope (403): degrade gracefully. */
31
+ get isScopeError(): boolean;
32
+ }
33
+ export declare const isOpenApiError: (e: unknown) => e is OpenApiError;
@@ -0,0 +1,70 @@
1
+ import type { EventType } from './gen/meta.ts';
2
+ export interface EventEnvelope<E = Record<string, unknown>> {
3
+ schema: '1.0';
4
+ header: {
5
+ event_id: string;
6
+ event_type: EventType;
7
+ create_time: string;
8
+ app_id: string;
9
+ tenant_key?: string | null;
10
+ };
11
+ event: E;
12
+ }
13
+ export interface UrlVerification {
14
+ type: 'url_verification';
15
+ challenge: string;
16
+ }
17
+ export interface CardAction {
18
+ schema: '1.0';
19
+ header: {
20
+ event_id: string;
21
+ app_id: string;
22
+ tenant_key?: string | null;
23
+ create_time: string;
24
+ };
25
+ operator?: {
26
+ open_id?: string;
27
+ };
28
+ action: {
29
+ value: Record<string, unknown>;
30
+ };
31
+ /** The conversation the card is in. */
32
+ chat_id?: string;
33
+ message_id?: string;
34
+ }
35
+ export type Headers = Record<string, string | string[] | undefined> | globalThis.Headers;
36
+ /** The signature the platform puts in `X-Ich-Signature` for these values. */
37
+ export declare function sign(signingSecret: string, timestamp: string, nonce: string, body: string | Buffer): string;
38
+ /**
39
+ * Checks a push's signature and age (default: at most 5 minutes off). Throws `OpenApiError` with
40
+ * code `INVALID_SIGNATURE` otherwise.
41
+ */
42
+ export declare function verifySignature(headers: Headers, rawBody: string | Buffer, signingSecret: string, opts?: {
43
+ toleranceSeconds?: number;
44
+ now?: number;
45
+ }): void;
46
+ /** Decrypts an `{"encrypt": …}` body with the app's encrypt key. */
47
+ export declare function decrypt(encrypted: string, encryptKey: string): string;
48
+ /**
49
+ * Verifies, decrypts and parses a push. Answer a `url_verification` with `{ challenge }`
50
+ * (`challengeResponse`), and an event with any 2xx within 5 seconds (dedupe by `header.event_id`:
51
+ * retries keep it).
52
+ */
53
+ export declare function parseEvent<E = Record<string, unknown>>(headers: Headers, rawBody: string | Buffer, opts: {
54
+ signingSecret: string;
55
+ encryptKey?: string;
56
+ toleranceSeconds?: number;
57
+ now?: number;
58
+ }): EventEnvelope<E> | UrlVerification;
59
+ /** A card callback (a button of a bot's card was clicked): same signature and encryption as events. */
60
+ export declare function parseCardAction(headers: Headers, rawBody: string | Buffer, opts: {
61
+ signingSecret: string;
62
+ encryptKey?: string;
63
+ toleranceSeconds?: number;
64
+ now?: number;
65
+ }): CardAction | UrlVerification;
66
+ export declare const isUrlVerification: (x: unknown) => x is UrlVerification;
67
+ /** The answer to the address check. */
68
+ export declare const challengeResponse: (v: UrlVerification) => {
69
+ challenge: string;
70
+ };
@@ -0,0 +1,44 @@
1
+ /** The SDK's version (it follows the open API's). */
2
+ export declare const VERSION = "1.0.0-rc.2";
3
+ /** The open API's address from the release configuration (sdks/release.json); `baseUrl` overrides it. */
4
+ export declare const DEFAULT_BASE_URL = "https://open.ichinese.cn";
5
+ export declare const API_PREFIX = "/open-apis/v1";
6
+ /** Every scope (permission code) of the open API. */
7
+ export declare const SCOPES: readonly ["openid", "profile", "org:member:readonly", "org:member", "class:readonly", "class:member", "schedule:readonly", "schedule", "attendance:readonly", "coursework", "grade:readonly", "course", "learning:progress:readonly", "im:message:send_as_bot", "im:chat:readonly", "notify:send", "live:recording:readonly", "event", "event:registration:readonly", "job", "job:candidate:readonly", "trial", "trade:order:readonly", "review", "profile:interest:readonly", "profile:interest", "profile:learning:readonly", "report:create"];
8
+ export type Scope = (typeof SCOPES)[number];
9
+ /** The events an app can subscribe to. */
10
+ export declare const EVENT_TYPES: readonly ["app.installed", "app.uninstalled", "app.scopes_changed", "coursework.submitted", "grade.published", "schedule.session_changed", "event.registered", "job.candidate_applied", "trade.order_paid", "review.created", "trial.booked", "im.message_received"];
11
+ export type EventType = (typeof EVENT_TYPES)[number];
12
+ /** `error.details.reason` values. */
13
+ export declare const ERROR_REASONS: readonly ["invalid_credentials", "invalid_token", "token_expired", "token_type", "app_disabled", "not_installed", "not_available", "scope_denied", "scope_pending", "scope_not_declared", "gold_only", "rate_limited", "daily_quota_exceeded", "code_expired", "code_used", "code_invalid", "redirect_uri_mismatch", "origin_not_trusted", "ip_not_allowed"];
14
+ export type ErrorReason = (typeof ERROR_REASONS)[number];
15
+ /** Token types each operation accepts (the client picks the token by them). */
16
+ export declare const OPERATION_TOKENS: Record<string, readonly ('app' | 'tenant' | 'user')[]>;
17
+ export declare const SCOPE_OPENID = "openid";
18
+ export declare const SCOPE_PROFILE = "profile";
19
+ export declare const SCOPE_ORG_MEMBER_READONLY = "org:member:readonly";
20
+ export declare const SCOPE_ORG_MEMBER = "org:member";
21
+ export declare const SCOPE_CLASS_READONLY = "class:readonly";
22
+ export declare const SCOPE_CLASS_MEMBER = "class:member";
23
+ export declare const SCOPE_SCHEDULE_READONLY = "schedule:readonly";
24
+ export declare const SCOPE_SCHEDULE = "schedule";
25
+ export declare const SCOPE_ATTENDANCE_READONLY = "attendance:readonly";
26
+ export declare const SCOPE_COURSEWORK = "coursework";
27
+ export declare const SCOPE_GRADE_READONLY = "grade:readonly";
28
+ export declare const SCOPE_COURSE = "course";
29
+ export declare const SCOPE_LEARNING_PROGRESS_READONLY = "learning:progress:readonly";
30
+ export declare const SCOPE_IM_MESSAGE_SEND_AS_BOT = "im:message:send_as_bot";
31
+ export declare const SCOPE_IM_CHAT_READONLY = "im:chat:readonly";
32
+ export declare const SCOPE_NOTIFY_SEND = "notify:send";
33
+ export declare const SCOPE_LIVE_RECORDING_READONLY = "live:recording:readonly";
34
+ export declare const SCOPE_EVENT = "event";
35
+ export declare const SCOPE_EVENT_REGISTRATION_READONLY = "event:registration:readonly";
36
+ export declare const SCOPE_JOB = "job";
37
+ export declare const SCOPE_JOB_CANDIDATE_READONLY = "job:candidate:readonly";
38
+ export declare const SCOPE_TRIAL = "trial";
39
+ export declare const SCOPE_TRADE_ORDER_READONLY = "trade:order:readonly";
40
+ export declare const SCOPE_REVIEW = "review";
41
+ export declare const SCOPE_PROFILE_INTEREST_READONLY = "profile:interest:readonly";
42
+ export declare const SCOPE_PROFILE_INTEREST = "profile:interest";
43
+ export declare const SCOPE_PROFILE_LEARNING_READONLY = "profile:learning:readonly";
44
+ export declare const SCOPE_REPORT_CREATE = "report:create";
@@ -0,0 +1,177 @@
1
+ import type { CallSpec, RequestOptions } from '../types.ts';
2
+ import type { operations } from './schema.ts';
3
+ type Op = keyof operations;
4
+ /** The query parameters of an operation. */
5
+ export type QueryOf<K extends Op> = operations[K] extends {
6
+ parameters: {
7
+ query?: infer P;
8
+ };
9
+ } ? NonNullable<P> : never;
10
+ /** The JSON request body of an operation. */
11
+ export type BodyOf<K extends Op> = operations[K] extends {
12
+ requestBody: {
13
+ content: {
14
+ 'application/json': infer T;
15
+ };
16
+ };
17
+ } ? T : never;
18
+ /** What an operation answers (its 2xx JSON body; `void` for 204). */
19
+ export type ResultOf<K extends Op> = operations[K]['responses'] extends infer R ? R extends {
20
+ 200: {
21
+ content: {
22
+ 'application/json': infer T;
23
+ };
24
+ };
25
+ } ? T : R extends {
26
+ 201: {
27
+ content: {
28
+ 'application/json': infer T;
29
+ };
30
+ };
31
+ } ? T : void : never;
32
+ /** One item of a paged list. */
33
+ export type ItemOf<K extends Op> = ResultOf<K> extends {
34
+ items: (infer I)[];
35
+ } ? I : never;
36
+ /** Every operation of the open API as a method; the client (client.ts) supplies tokens, retries and paging. */
37
+ export declare abstract class Operations {
38
+ protected abstract call<T>(spec: CallSpec, options?: RequestOptions): Promise<T>;
39
+ protected abstract iterate<T>(spec: CallSpec, options?: RequestOptions): AsyncIterable<T>;
40
+ /** The current user (user token) (`GET /user/info`; user token; scopes: openid) */
41
+ getUserInfo(options?: RequestOptions): Promise<ResultOf<'getUserInfo'>>;
42
+ /** The institution that installed the app (tenant token) (`GET /tenant`; tenant token) */
43
+ getTenant(options?: RequestOptions): Promise<ResultOf<'getTenant'>>;
44
+ /** 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) */
45
+ listTenantMembers(query?: QueryOf<'listTenantMembers'>, options?: RequestOptions): Promise<ResultOf<'listTenantMembers'>>;
46
+ /** Every item of `listTenantMembers`, page after page. */
47
+ listTenantMembersAll(query?: Omit<QueryOf<'listTenantMembers'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTenantMembers'>>;
48
+ /** Invite a member (they join only after accepting, the same as inviting in the institution admin) (`POST /tenant/invitations`; tenant token; scopes: org:member) */
49
+ inviteTenantMember(body: BodyOf<'inviteTenantMember'>, options?: RequestOptions): Promise<ResultOf<'inviteTenantMember'>>;
50
+ /** 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) */
51
+ listClasses(query?: QueryOf<'listClasses'>, options?: RequestOptions): Promise<ResultOf<'listClasses'>>;
52
+ /** Every item of `listClasses`, page after page. */
53
+ listClassesAll(query?: Omit<QueryOf<'listClasses'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listClasses'>>;
54
+ /** A class (`GET /classes/{class_id}`; tenant / user token; scopes: class:readonly, class:member) */
55
+ getClass(classId: string, options?: RequestOptions): Promise<ResultOf<'getClass'>>;
56
+ /** Roster (teachers and learners) (`GET /classes/{class_id}/members`; tenant / user token; scopes: class:readonly, class:member) */
57
+ listClassMembers(classId: string, query?: QueryOf<'listClassMembers'>, options?: RequestOptions): Promise<ResultOf<'listClassMembers'>>;
58
+ /** Every item of `listClassMembers`, page after page. */
59
+ listClassMembersAll(classId: string, query?: Omit<QueryOf<'listClassMembers'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listClassMembers'>>;
60
+ /** Add one of the institution's members to a class (as a learner) (`POST /classes/{class_id}/members`; tenant / user token; scopes: class:member) */
61
+ addClassMember(classId: string, body: BodyOf<'addClassMember'>, options?: RequestOptions): Promise<ResultOf<'addClassMember'>>;
62
+ /** Remove a learner from a class (`DELETE /classes/{class_id}/members/{open_id}`; tenant / user token; scopes: class:member) */
63
+ removeClassMember(classId: string, openId: string, options?: RequestOptions): Promise<ResultOf<'removeClassMember'>>;
64
+ /** 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) */
65
+ listClassSessions(classId: string, query: QueryOf<'listClassSessions'>, options?: RequestOptions): Promise<ResultOf<'listClassSessions'>>;
66
+ /** Every item of `listClassSessions`, page after page. */
67
+ listClassSessionsAll(classId: string, query?: Omit<QueryOf<'listClassSessions'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listClassSessions'>>;
68
+ /** 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) */
69
+ cancelClassSession(classId: string, sessionId: string, options?: RequestOptions): Promise<ResultOf<'cancelClassSession'>>;
70
+ /** Reschedule a class session (`POST /classes/{class_id}/sessions/{session_id}/reschedule`; tenant / user token; scopes: schedule) */
71
+ rescheduleClassSession(classId: string, sessionId: string, body: BodyOf<'rescheduleClassSession'>, options?: RequestOptions): Promise<ResultOf<'rescheduleClassSession'>>;
72
+ /** 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) */
73
+ getSessionAttendance(classId: string, sessionId: string, options?: RequestOptions): Promise<ResultOf<'getSessionAttendance'>>;
74
+ /** A class's assignments and exams (`GET /classes/{class_id}/assignments`; tenant / user token; scopes: coursework, grade:readonly) */
75
+ listAssignments(classId: string, query?: QueryOf<'listAssignments'>, options?: RequestOptions): Promise<ResultOf<'listAssignments'>>;
76
+ /** Every item of `listAssignments`, page after page. */
77
+ listAssignmentsAll(classId: string, query?: Omit<QueryOf<'listAssignments'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listAssignments'>>;
78
+ /** 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) */
79
+ createAssignment(classId: string, body: BodyOf<'createAssignment'>, options?: RequestOptions): Promise<ResultOf<'createAssignment'>>;
80
+ /** An assignment's submissions and scores (`GET /classes/{class_id}/assignments/{assignment_id}/submissions`; tenant / user token; scopes: grade:readonly) */
81
+ listSubmissions(classId: string, assignmentId: string, query?: QueryOf<'listSubmissions'>, options?: RequestOptions): Promise<ResultOf<'listSubmissions'>>;
82
+ /** Every item of `listSubmissions`, page after page. */
83
+ listSubmissionsAll(classId: string, assignmentId: string, query?: Omit<QueryOf<'listSubmissions'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listSubmissions'>>;
84
+ /** 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) */
85
+ setSubmissionScore(classId: string, assignmentId: string, openId: string, body: BodyOf<'setSubmissionScore'>, options?: RequestOptions): Promise<ResultOf<'setSubmissionScore'>>;
86
+ /** Export grades (one row per learner per assignment) (`GET /classes/{class_id}/grades`; tenant / user token; scopes: grade:readonly) */
87
+ exportClassGrades(classId: string, query?: QueryOf<'exportClassGrades'>, options?: RequestOptions): Promise<ResultOf<'exportClassGrades'>>;
88
+ /** Every item of `exportClassGrades`, page after page. */
89
+ exportClassGradesAll(classId: string, query?: Omit<QueryOf<'exportClassGrades'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'exportClassGrades'>>;
90
+ /** 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) */
91
+ listCourses(query?: QueryOf<'listCourses'>, options?: RequestOptions): Promise<ResultOf<'listCourses'>>;
92
+ /** Every item of `listCourses`, page after page. */
93
+ listCoursesAll(query?: Omit<QueryOf<'listCourses'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listCourses'>>;
94
+ /** Course outline (titles and order of chapters and lessons) (`GET /courses/{course_id}`; tenant / user token; scopes: course) */
95
+ getCourseOutline(courseId: string, options?: RequestOptions): Promise<ResultOf<'getCourseOutline'>>;
96
+ /** 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) */
97
+ updateCourseOutline(courseId: string, body: BodyOf<'updateCourseOutline'>, options?: RequestOptions): Promise<ResultOf<'updateCourseOutline'>>;
98
+ /** 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) */
99
+ listCourseProgress(courseId: string, query?: QueryOf<'listCourseProgress'>, options?: RequestOptions): Promise<ResultOf<'listCourseProgress'>>;
100
+ /** Every item of `listCourseProgress`, page after page. */
101
+ listCourseProgressAll(courseId: string, query?: Omit<QueryOf<'listCourseProgress'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listCourseProgress'>>;
102
+ /** 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) */
103
+ sendBotMessage(body: BodyOf<'sendBotMessage'>, options?: RequestOptions): Promise<ResultOf<'sendBotMessage'>>;
104
+ /** Group chats the bot is in (`GET /im/chats`; tenant / app token; scopes: im:chat:readonly) */
105
+ listBotChats(query?: QueryOf<'listBotChats'>, options?: RequestOptions): Promise<ResultOf<'listBotChats'>>;
106
+ /** Every item of `listBotChats`, page after page. */
107
+ listBotChatsAll(query?: Omit<QueryOf<'listBotChats'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listBotChats'>>;
108
+ /** 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) */
109
+ sendNotification(body: BodyOf<'sendNotification'>, options?: RequestOptions): Promise<ResultOf<'sendNotification'>>;
110
+ /** 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) */
111
+ listClassRecordings(classId: string, query?: QueryOf<'listClassRecordings'>, options?: RequestOptions): Promise<ResultOf<'listClassRecordings'>>;
112
+ /** Every item of `listClassRecordings`, page after page. */
113
+ listClassRecordingsAll(classId: string, query?: Omit<QueryOf<'listClassRecordings'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listClassRecordings'>>;
114
+ /** The institution's events (including drafts and those pending review) (`GET /events`; tenant token; scopes: event, event:registration:readonly) */
115
+ listTenantEvents(query?: QueryOf<'listTenantEvents'>, options?: RequestOptions): Promise<ResultOf<'listTenantEvents'>>;
116
+ /** Every item of `listTenantEvents`, page after page. */
117
+ listTenantEventsAll(query?: Omit<QueryOf<'listTenantEvents'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTenantEvents'>>;
118
+ /** Create an event (a draft; the institution publishes it in its console, moderated as usual) (`POST /events`; tenant token; scopes: event) */
119
+ createTenantEvent(body: BodyOf<'createTenantEvent'>, options?: RequestOptions): Promise<ResultOf<'createTenantEvent'>>;
120
+ /** Change an event (the same fields as event editing in the platform's institution console) (`PATCH /events/{event_id}`; tenant token; scopes: event) */
121
+ updateTenantEvent(eventId: string, body: BodyOf<'updateTenantEvent'>, options?: RequestOptions): Promise<ResultOf<'updateTenantEvent'>>;
122
+ /** An event's registrations and check-ins (`GET /events/{event_id}/registrations`; tenant token; scopes: event:registration:readonly) */
123
+ listEventRegistrations(eventId: string, query?: QueryOf<'listEventRegistrations'>, options?: RequestOptions): Promise<ResultOf<'listEventRegistrations'>>;
124
+ /** Every item of `listEventRegistrations`, page after page. */
125
+ listEventRegistrationsAll(eventId: string, query?: Omit<QueryOf<'listEventRegistrations'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listEventRegistrations'>>;
126
+ /** The institution's job postings (including drafts and those pending review) (`GET /jobs`; tenant token; scopes: job, job:candidate:readonly) */
127
+ listTenantJobs(query?: QueryOf<'listTenantJobs'>, options?: RequestOptions): Promise<ResultOf<'listTenantJobs'>>;
128
+ /** Every item of `listTenantJobs`, page after page. */
129
+ listTenantJobsAll(query?: Omit<QueryOf<'listTenantJobs'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTenantJobs'>>;
130
+ /** 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) */
131
+ createTenantJob(body: BodyOf<'createTenantJob'>, options?: RequestOptions): Promise<ResultOf<'createTenantJob'>>;
132
+ /** 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) */
133
+ listJobCandidates(jobId: string, query?: QueryOf<'listJobCandidates'>, options?: RequestOptions): Promise<ResultOf<'listJobCandidates'>>;
134
+ /** Every item of `listJobCandidates`, page after page. */
135
+ listJobCandidatesAll(jobId: string, query?: Omit<QueryOf<'listJobCandidates'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listJobCandidates'>>;
136
+ /** The institution's teaching sites (use their venue_id to schedule trial slots) (`GET /trial/venues`; tenant token; scopes: trial) */
137
+ listTrialVenues(query?: QueryOf<'listTrialVenues'>, options?: RequestOptions): Promise<ResultOf<'listTrialVenues'>>;
138
+ /** Every item of `listTrialVenues`, page after page. */
139
+ listTrialVenuesAll(query?: Omit<QueryOf<'listTrialVenues'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTrialVenues'>>;
140
+ /** Trial lesson slots (by default only those not over yet, by time) (`GET /trial/slots`; tenant token; scopes: trial) */
141
+ listTrialSlots(query?: QueryOf<'listTrialSlots'>, options?: RequestOptions): Promise<ResultOf<'listTrialSlots'>>;
142
+ /** Every item of `listTrialSlots`, page after page. */
143
+ listTrialSlotsAll(query?: Omit<QueryOf<'listTrialSlots'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTrialSlots'>>;
144
+ /** Schedule trial lesson slots (they can repeat weekly); the teaching site must be published (`POST /trial/slots`; tenant token; scopes: trial) */
145
+ createTrialSlots(body: BodyOf<'createTrialSlots'>, options?: RequestOptions): Promise<ResultOf<'createTrialSlots'>>;
146
+ /** 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) */
147
+ cancelTrialSlot(slotId: string, body?: BodyOf<'cancelTrialSlot'>, options?: RequestOptions): Promise<ResultOf<'cancelTrialSlot'>>;
148
+ /** Trial lesson bookings at the institution's teaching sites (`GET /trial/bookings`; tenant token; scopes: trial) */
149
+ listTrialBookings(query?: QueryOf<'listTrialBookings'>, options?: RequestOptions): Promise<ResultOf<'listTrialBookings'>>;
150
+ /** Every item of `listTrialBookings`, page after page. */
151
+ listTrialBookingsAll(query?: Omit<QueryOf<'listTrialBookings'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTrialBookings'>>;
152
+ /** Confirm a trial lesson booking (`POST /trial/bookings/{booking_id}/confirm`; tenant token; scopes: trial) */
153
+ confirmTrialBooking(bookingId: string, options?: RequestOptions): Promise<ResultOf<'confirmTrialBooking'>>;
154
+ /** The institution's orders (read-only, for reconciliation). Write operations involving money are not open (`GET /trade/orders`; tenant token; scopes: trade:order:readonly) */
155
+ listTradeOrders(query?: QueryOf<'listTradeOrders'>, options?: RequestOptions): Promise<ResultOf<'listTradeOrders'>>;
156
+ /** Every item of `listTradeOrders`, page after page. */
157
+ listTradeOrdersAll(query?: Omit<QueryOf<'listTradeOrders'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTradeOrders'>>;
158
+ /** Reviews of this institution (and its courses and teaching sites) (`GET /reviews`; tenant token; scopes: review) */
159
+ listTenantReviews(query?: QueryOf<'listTenantReviews'>, options?: RequestOptions): Promise<ResultOf<'listTenantReviews'>>;
160
+ /** Every item of `listTenantReviews`, page after page. */
161
+ listTenantReviewsAll(query?: Omit<QueryOf<'listTenantReviews'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listTenantReviews'>>;
162
+ /** 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) */
163
+ replyReview(reviewId: string, body: BodyOf<'replyReview'>, options?: RequestOptions): Promise<ResultOf<'replyReview'>>;
164
+ /** Public courses, events and jobs shown in this region (`GET /public/{kind}`; app / tenant / user token) */
165
+ listPublicContent(kind: string, query?: QueryOf<'listPublicContent'>, options?: RequestOptions): Promise<ResultOf<'listPublicContent'>>;
166
+ /** Every item of `listPublicContent`, page after page. */
167
+ listPublicContentAll(kind: string, query?: Omit<QueryOf<'listPublicContent'>, 'page_token'>, options?: RequestOptions): AsyncIterable<ItemOf<'listPublicContent'>>;
168
+ /** 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) */
169
+ getUserInterests(options?: RequestOptions): Promise<ResultOf<'getUserInterests'>>;
170
+ /** Add an interest signal with its source (`POST /user/profile/interest_signals`; user token; scopes: profile:interest) */
171
+ addInterestSignal(body: BodyOf<'addInterestSignal'>, options?: RequestOptions): Promise<ResultOf<'addInterestSignal'>>;
172
+ /** The user's learning profile (level, goals, languages being learned) (`GET /user/profile/learning`; user token; scopes: profile:learning:readonly) */
173
+ getUserLearningProfile(options?: RequestOptions): Promise<ResultOf<'getUserLearningProfile'>>;
174
+ /** 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) */
175
+ reportOnBehalf(body: BodyOf<'reportOnBehalf'>, options?: RequestOptions): Promise<ResultOf<'reportOnBehalf'>>;
176
+ }
177
+ export {};