import Chat from "./Chat.ts"; interface FetchOptions extends RequestInit { client?: Deno.HttpClient; } interface Category { name: string; description: string; } interface Featured { characters: string[]; name: string; } interface CharacterInfo { external_id: string; title: string; name: string; visibility: string; copyable: false; greeting: string; description: string; identifier: string; avatar_file_name: string; songs: unknown[]; img_gen_enabled: false; base_img_prompt: string; img_prompt_regex: string; strip_img_prompt_from_msg: boolean; user__username: string; participant__name: string; participant__num_interactions: number; participant__user__username: string; voice_id: number; usage: string; } interface SearchResult { external_id: string; title: string; greeting: string; description: string; avatar_file_name: string; visibility: string; copyable: boolean; participant__name: string; participant__num_interactions: number; user__id: number; user__username: string; img_gen_enabled: boolean; priority: number; search_score: number; } export default class Client { public token?: string; #client?: Deno.HttpClient; /** * Initalizes a HTTP(S) proxy for each request (Requires --unstable) * @param proxyUrl URL of the proxy * @param certPath Path to a certificate file */ public async intitalizeProxy(proxyUrl: string, certPath: string | URL) { this.#client = Deno.createHttpClient({ caCerts: [await Deno.readTextFile(certPath)], proxy: { url: proxyUrl }, }); } async fetch( input: string | URL | Request, options?: FetchOptions | undefined ) { if (this.#client) options = { client: this.#client, ...options, }; return await fetch(input, options); } /** * Authenticates with character.ai * @param authToken The authorization token */ public async authenticate(authToken: string) { const res = await this.fetch( "https://beta.character.ai/dj-rest-auth/auth0/", { body: JSON.stringify({ access_token: authToken, }), method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, } ); const json = await res.json(); this.token = json.key; if (!this.token) throw new Error("Authentication failed, your token might be invalid"); } /** * Fetches a list of categories * @returns List of categories */ public async fetchCategories(): Promise { const res = await this.fetch( "https://beta.character.ai/chat/character/categories/" ); const { categories } = await res.json(); if (!Array.isArray(categories)) throw new Error("API provided invalid category data"); return categories; } /** * Fetches a list of featured characters * @returns List of featured characters */ public async fetchFeaturedCharacters(): Promise { const res = await this.fetch( "https://beta.character.ai/chat/characters/featured/", { headers: { Authorization: `Token ${this.token}`, Accept: "application/json", "Content-Type": "application/json", }, } ); const { featured_characters } = await res.json(); if (!Array.isArray(featured_characters)) throw new Error("API provided invalid featured characters data"); return featured_characters; } /** * Fetches information for a character * @param id Id of the character * @returns Character information */ public async fetchCharacterInfo(id: string): Promise { const res = await this.fetch( "https://beta.character.ai/chat/character/info/", { body: JSON.stringify({ external_id: id, }), method: "POST", headers: { Authorization: `Token ${this.token}`, Accept: "application/json", "Content-Type": "application/json", }, } ); const { character } = await res.json(); return character; } /** * Search for characters * @param query Search query * @returns Array of characters */ public async search(query: string): Promise { const url = new URL( "https://beta.character.ai/chat/characters/search/" ); url.searchParams.set("query", query); const res = await this.fetch(url, { headers: { Authorization: `Token ${this.token}`, Accept: "application/json", "Content-Type": "application/json", }, }); const { characters } = await res.json(); if (!Array.isArray(characters)) throw new Error("API provided invalid search response"); return characters; } /** * Creates a new chat history * @param id Character id * @returns The raw history to be used with the Chat class */ public async createChat(id: string) { const res = await this.fetch( "https://beta.character.ai/chat/history/create/", { body: JSON.stringify({ character_external_id: id, history_external_id: null, }), method: "POST", headers: { Authorization: `Token ${this.token}`, Accept: "application/json", "Content-Type": "application/json", }, } ); const history = await res.json(); return history; } /** * Continues a chat * @param id Character id * @returns The raw history to be used with the Chat class or false if the chat doesn't exist */ public async continueChat(id: string): Promise { const res = await this.fetch( "https://beta.character.ai/chat/history/continue/", { body: JSON.stringify({ character_external_id: id, history_external_id: null, }), method: "POST", headers: { Authorization: `Token ${this.token}`, Accept: "application/json", "Content-Type": "application/json", }, } ); const history = await res.json(); if (history.status === "No Such History") return false; return history; } /** * Continues a chat or creates a new one if it doesn't exist * @param id Character id * @returns The Chat class */ public async continueOrCreateChat(id: string): Promise { let history = await this.continueChat(id); if (!history) history = await this.createChat(id); return new Chat(this, id, history); } }