You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
6.7 KiB
268 lines
6.7 KiB
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<Category[]> {
|
|
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<Featured[]> {
|
|
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<CharacterInfo> {
|
|
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<SearchResult[]> {
|
|
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<Chat | false> {
|
|
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<Chat> {
|
|
let history = await this.continueChat(id);
|
|
|
|
if (!history) history = await this.createChat(id);
|
|
|
|
return new Chat(this, id, history);
|
|
}
|
|
}
|