import axios, { AxiosInstance } from "axios"; import decamelizeKeys from "decamelize-keys"; import camelcaseKeys from "camelcase-keys"; const ONE_MINUTE = 1000 * 60; // 1 minute export class Client { public api: AxiosInstance; public baseUrl: string; public logger: any; constructor(options: { baseUrl: string; accessToken?: string; logger?: any; locale?: "en" | "zh-CN"; }) { const { baseUrl, accessToken, logger, locale = "en" } = options; this.baseUrl = baseUrl; this.logger = logger || console; this.api = axios.create({ baseURL: baseUrl, timeout: ONE_MINUTE, headers: { "Content-Type": "application/json", }, }); this.api.interceptors.request.use((config) => { config.headers.Authorization = `Bearer ${accessToken}`; config.headers["Accept-Language"] = locale; this.logger.debug( config.method.toUpperCase(), config.baseURL + config.url, config.data, config.params ); return config; }); this.api.interceptors.response.use( (response) => { this.logger.debug( response.status, response.config.method.toUpperCase(), response.config.baseURL + response.config.url ); return camelcaseKeys(response.data, { deep: true }); }, (err) => { if (err.response) { this.logger.error( err.response.status, err.response.config.method.toUpperCase(), err.response.config.baseURL + err.response.config.url ); this.logger.error(err.response.data); return Promise.reject(new Error(err.response.data)); } this.logger.error(err.message); return Promise.reject(err); } ); } auth(params: { provider: "mixin" | "github" | "bandu" | "email"; code?: string; deviceCode?: string; phoneNumber?: string; email?: string; mixinId?: string; }): Promise { return this.api.post("/api/sessions", decamelizeKeys(params)); } config(key: string): Promise { return this.api.get(`/api/config/${key}`); } deviceCode(provider = "github"): Promise<{ deviceCode: string; userCode: string; verificationUri: string; expiresIn: number; interval: number; }> { return this.api.post("/api/sessions/device_code", { provider }); } me(): Promise { return this.api.get("/api/me"); } updateProfile( id: string, params: { name?: string; email?: string; code?: string; } ): Promise { return this.api.put(`/api/users/${id}`, decamelizeKeys(params)); } loginCode(params: { phoneNumber?: string; email?: string; mixinId?: string; }): Promise { return this.api.post("/api/sessions/login_code", decamelizeKeys(params)); } rankings(range: "day" | "week" | "month" | "year" | "all" = "day"): Promise<{ rankings: UserType[]; range: string; }> { return this.api.get("/api/users/rankings", { params: { range } }); } users(filter: "following" | "followers" = "followers"): Promise< { users: UserType[]; } & PagyResponseType > { return this.api.get("/api/users", { params: { filter } }); } user(id: string): Promise { return this.api.get(`/api/users/${id}`); } userFollowing( id: string, options: { page: number } ): Promise< { users: UserType[]; } & PagyResponseType > { return this.api.get(`/api/users/${id}/following`, { params: decamelizeKeys(options), }); } userFollowers( id: string, options: { page: number } ): Promise< { users: UserType[]; } & PagyResponseType > { return this.api.get(`/api/users/${id}/followers`, { params: decamelizeKeys(options), }); } follow(id: string): Promise< { user: UserType; } & { following: boolean; } > { return this.api.post(`/api/users/${id}/follow`); } unfollow(id: string): Promise< { user: UserType; } & { following: boolean; } > { return this.api.post(`/api/users/${id}/unfollow`); } posts(params?: { page?: number; items?: number; userId?: string; type?: | "all" | "recording" | "medium" | "story" | "prompt" | "text" | "gpt" | "note"; by?: "following" | "all"; }): Promise< { posts: PostType[]; } & PagyResponseType > { return this.api.get("/api/posts", { params: decamelizeKeys(params) }); } post(id: string): Promise { return this.api.get(`/api/posts/${id}`); } createPost(params: { metadata?: PostType["metadata"]; targetType?: string; targetId?: string; }): Promise { return this.api.post("/api/posts", decamelizeKeys(params)); } updatePost(id: string, params: { content: string }): Promise { return this.api.put(`/api/posts/${id}`, decamelizeKeys(params)); } deletePost(id: string): Promise { return this.api.delete(`/api/posts/${id}`); } likePost(id: string): Promise { return this.api.post(`/api/posts/${id}/like`); } unlikePost(id: string): Promise { return this.api.delete(`/api/posts/${id}/unlike`); } transcriptions(params?: { page?: number; items?: number; targetId?: string; targetType?: string; targetMd5?: string; }): Promise< { transcriptions: TranscriptionType[]; } & PagyResponseType > { return this.api.get("/api/transcriptions", { params: decamelizeKeys(params), }); } syncAudio(audio: Partial) { return this.api.post("/api/mine/audios", decamelizeKeys(audio)); } deleteAudio(id: string) { return this.api.delete(`/api/mine/audios/${id}`); } syncVideo(video: Partial) { return this.api.post("/api/mine/videos", decamelizeKeys(video)); } deleteVideo(id: string) { return this.api.delete(`/api/mine/videos/${id}`); } syncTranscription(transcription: Partial) { return this.api.post("/api/transcriptions", decamelizeKeys(transcription)); } syncSegment( segment: Partial> ) { return this.api.post("/api/segments", decamelizeKeys(segment)); } syncNote(note: Partial>) { return this.api.post("/api/notes", decamelizeKeys(note)); } deleteNote(id: string) { return this.api.delete(`/api/notes/${id}`); } syncRecording(recording: Partial) { if (!recording) return; return this.api.post("/api/mine/recordings", decamelizeKeys(recording)); } deleteRecording(id: string) { return this.api.delete(`/api/mine/recordings/${id}`); } generateSpeechToken(params?: { purpose?: string; targetType?: string; targetId?: string; input?: string; }): Promise<{ id: number; token: string; region: string }> { return this.api.post("/api/speech/tokens", decamelizeKeys(params || {})); } consumeSpeechToken(id: number) { return this.api.put(`/api/speech/tokens/${id}`, { state: "consumed", }); } revokeSpeechToken(id: number) { return this.api.put(`/api/speech/tokens/${id}`, { state: "revoked", }); } syncPronunciationAssessment( pronunciationAssessment: Partial ) { if (!pronunciationAssessment) return; return this.api.post( "/api/mine/pronunciation_assessments", decamelizeKeys(pronunciationAssessment) ); } recordingAssessment(id: string) { return this.api.get(`/api/mine/recordings/${id}/assessment`); } lookup(params: { word: string; context: string; sourceId?: string; sourceType?: string; nativeLanguage?: string; }): Promise { return this.api.post("/api/lookups", decamelizeKeys(params)); } updateLookup( id: string, params: { meaning: Partial; sourceId?: string; sourceType?: string; } ): Promise { return this.api.put(`/api/lookups/${id}`, decamelizeKeys(params)); } lookupInBatch( lookups: { word: string; context: string; sourceId?: string; sourceType?: string; }[] ): Promise<{ successCount: number; errors: string[]; total: number }> { return this.api.post("/api/lookups/batch", { lookups: decamelizeKeys(lookups, { deep: true }), }); } extractVocabularyFromStory( storyId: string, extraction?: { words?: string[]; idioms?: string[]; } ): Promise { return this.api.post( `/api/stories/${storyId}/extract_vocabulary`, decamelizeKeys({ extraction }) ); } storyMeanings( storyId: string, params?: { page?: number; items?: number; } ): Promise< { meanings: MeaningType[]; pendingLookups?: LookupType[]; } & PagyResponseType > { return this.api.get(`/api/stories/${storyId}/meanings`, { params: decamelizeKeys(params), }); } mineMeanings(params?: { page?: number; items?: number; sourceId?: string; sourceType?: string; status?: string; }): Promise< { meanings: MeaningType[]; } & PagyResponseType > { return this.api.get("/api/mine/meanings", { params: decamelizeKeys(params), }); } createStory(params: CreateStoryParamsType): Promise { return this.api.post("/api/stories", decamelizeKeys(params)); } story(id: string): Promise { return this.api.get(`/api/stories/${id}`); } stories(params?: { page: number }): Promise< { stories: StoryType[]; } & PagyResponseType > { return this.api.get("/api/stories", { params: decamelizeKeys(params) }); } mineStories(params?: { page: number }): Promise< { stories: StoryType[]; } & PagyResponseType > { return this.api.get("/api/mine/stories", { params: decamelizeKeys(params), }); } starStory(storyId: string): Promise<{ starred: boolean }> { return this.api.post(`/api/mine/stories`, decamelizeKeys({ storyId })); } unstarStory(storyId: string): Promise<{ starred: boolean }> { return this.api.delete(`/api/mine/stories/${storyId}`); } createPayment(params: { amount: number; reconciledCurrency: string; processor: string; paymentType: string; }): Promise { return this.api.post("/api/payments", decamelizeKeys(params)); } payments(params?: { paymentType?: string; page?: number; items?: number; }): Promise< { payments: PaymentType[]; } & PagyResponseType > { return this.api.get("/api/payments", { params: decamelizeKeys(params) }); } payment(id: string): Promise { return this.api.get(`/api/payments/${id}`); } mineSegments(params?: { page?: number; segmentIndex?: number; targetId?: string; targetType?: string; }): Promise< { segments: SegmentType[]; } & PagyResponseType > { return this.api.get("/api/mine/segments", { params: decamelizeKeys(params), }); } courses(params?: { language?: string; page?: number; items?: number; query?: string; }): Promise< { courses: CourseType[]; } & PagyResponseType > { return this.api.get("/api/courses", { params: decamelizeKeys(params) }); } course(id: string): Promise { return this.api.get(`/api/courses/${id}`); } createEnrollment(courseId: string): Promise { return this.api.post(`/api/enrollments`, decamelizeKeys({ courseId })); } courseChapters( courseId: string, params?: { page?: number; items?: number; query?: string; } ): Promise< { chapters: ChapterType[]; } & PagyResponseType > { return this.api.get(`/api/courses/${courseId}/chapters`, { params: decamelizeKeys(params), }); } coursechapter(courseId: string, id: number | string): Promise { return this.api.get(`/api/courses/${courseId}/chapters/${id}`); } finishCourseChapter(courseId: string, id: number | string): Promise { return this.api.post(`/api/courses/${courseId}/chapters/${id}/finish`); } enrollments(params?: { page?: number; items?: number }): Promise< { enrollments: EnrollmentType[]; } & PagyResponseType > { return this.api.get("/api/enrollments", { params: decamelizeKeys(params) }); } updateEnrollment( id: string, params: { currentChapterId?: string; } ): Promise { return this.api.put(`/api/enrollments/${id}`, decamelizeKeys(params)); } createLlmChat(params: { agentId: string; agentType: string; }): Promise { return this.api.post("/api/chats", decamelizeKeys(params)); } llmChat(id: string): Promise { return this.api.get(`/api/chats/${id}`); } createLlmMessage( chatId: string, params: { query: string; agentId?: string; agentType?: string; } ): Promise { return this.api.post( `/api/chats/${chatId}/messages`, decamelizeKeys(params) ); } llmMessages( chatId: string, params: { page?: number; items?: number; } ): Promise< { messages: LlmMessageType[]; } & PagyResponseType > { return this.api.get(`/api/chats/${chatId}/messages`, { params: decamelizeKeys(params), }); } }