Skip to content

Instantly share code, notes, and snippets.

@catvinyl
Last active November 29, 2023 00:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save catvinyl/f7fa537954265f0ac3b940a114745b04 to your computer and use it in GitHub Desktop.
Save catvinyl/f7fa537954265f0ac3b940a114745b04 to your computer and use it in GitHub Desktop.
characterai.js
// License: CC0 1.0 Universal (CC0 1.0) / Public domain (PD)
// Name: Ask CharacterAI
const WebSocket = require('ws');
let crypto;
try {
crypto = require('node:crypto');
} catch (err) {
console.error('crypto support is disabled!');
}
var debug = false;
function dbg(...a){
if (!debug) return;
console.log(...a);
}
function ws_log(...a) {
dbg(...a);
}
function createAuthorHuman(author_id, name) {
return {
author_id: author_id.toString(),
name: name.toString(),
is_human: true
}
}
function parseMessage(data) {
var o;
try {
o = JSON.parse(data);
ws_log('@Received: ', o);
} catch (error) {
ws_log('@Received: ', data);
}
return o;
}
function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
function generate_cmd_ask(author, chat_id, character_id, text, obj, resolve, stream) {
const turn_id = uuidv4();
const request_id = uuidv4();
var o = {
"command": "create_and_generate_turn",
"request_id": request_id,
"payload": {
"num_candidates": 1,
"character_id": character_id,
"turn": {
"turn_key": {
"turn_id": turn_id,
"chat_id": chat_id
},
"author": author,
"candidates": [{
"candidate_id": turn_id,
"raw_content": text,
}], "primary_candidate_id": turn_id
}
}
};
if(obj){
return request(obj, o, resolve, {stream: stream});
}
return o;
}
async function request(obj, o, resolve, args) {
if (obj.ws.readyState != 1) {
await obj.connect();
}
const req = JSON.stringify(o);
await obj.ws.send(req);
if(!o.request_id){
o.request_id = uuidv4();
}
ws_log('@Request: ' + req);
const request = { request_id: o.request_id, resolve: resolve, o: o, args: args || {}};
return obj.requests.push(request);
}
function findRequest (obj, response){
if (typeof response.request_id != 'string') {
return 0;
}
for (let i = 0; i < obj.requests.length; i++) {
const element = obj.requests[i];
if (element.request_id == response.request_id) {
return i;
}
}
}
function createCharAI(token) {
var obj = {};
obj.token = token;
obj.requests = [];
obj.user_id = null;
obj.connect = async function (token) {
if (token) {
obj.token = token;
}
const options = {
headers: {
Cookie: 'HTTP_AUTHORIZATION="Token ' + obj.token + '"'
}
};
obj.ws = new WebSocket('wss://neo.character.ai/ws/', options);
return new Promise((resolve, reject) => {
obj.ws.on('open', function open() {
resolve(obj.ws);
});
obj.ws.on('error', console.error);
obj.ws.on('message', function (data) {
const response = parseMessage(data);
if(!response) return;
const turn = response.turn;
const i = findRequest(obj, response);
if(typeof i != 'number'){
return;
}
const element = obj.requests[i];
if(!element) return;
dbg(element, 'element');
var resolve = false;
if (response.comment){
if (response.comment.startsWith('Unauthorized access for user_id:')){
obj.user_id = response.comment.replace(/[^0-9]/g, ''); // get numbers
element.resolve(true);
return obj.requests.splice(i, 1);
}
}
if (response.command == 'neo_error') resolve = true;
if (turn) {
if (turn.state == 'STATE_OK') {
resolve = true;
}
}
if(response.command == 'add_turn'){
if (element.args.stream){
return element.args.stream(response);
}
}
if (resolve) {
if (element){
if (element.resolve){
element.resolve(response);
obj.requests.splice(i, 1);
}
}
}
});
});
}
obj.ask = async function (author, chat_id, character_id, text, stream) {
return new Promise((resolve, reject) => {
generate_cmd_ask(author, chat_id, character_id, text, obj, resolve, stream);
});
}
obj.getUserId = async function (){
await obj.createChat();
}
obj.createChat = async function (character_id) {
if (character_id){
if(!obj.user_id){
await obj.getUserId();
}
}
if (!character_id){
character_id = '0';
}
const chat_id = uuidv4();
var creator_id = '0';
if(obj.user_id){
creator_id = obj.user_id;
}
const req = {
"command": "create_chat", "payload":
{
"chat": {
"chat_id": chat_id,
"creator_id": creator_id,
"visibility": "VISIBILITY_PRIVATE",
"character_id": character_id,
"type": "TYPE_ONE_ON_ONE"
}, "with_greeting": true
}
};
return new Promise((resolve, reject) => {
request(obj, req, resolve);
});
}
return obj;
}
function getChatId(chat){
return chat.turn.turn_key.chat_id;
}
exports.createCharAI = createCharAI;
exports.createAuthorHuman = createAuthorHuman;
exports.getChatId = getChatId;
function test_onstream(response) {
const turn = response.turn;
if (turn.author.is_human) return;
const author_name = turn.author.name;
const text = turn.candidates[0].raw_content;
console.log('💬 ' + author_name + ': ' + text);
}
async function test() {
debug = false;
const questions = ['Hello!', 'Who are you?']
const token = '123456789abcdef123456789abcdef123456789a';
if(!token){
return console.log('Provide token please!');
}
const characterAI = createCharAI(token);
await characterAI.connect(token);
console.log('Ready!');
const character_id = 'kP-kNGIi7VswePS8cw-q1Wd_6NcJMeAJkqkJqWjM9Cc'; // Adolf Hitler
const chat = await characterAI.createChat(character_id);
const chat_id = getChatId(chat);
// const chat_id = 'xxxxyyyy-bbbb-cccc-dddd-eeeeffff1111'; // You continue chat if you know chat_id
console.log('user_id', characterAI.user_id);
console.log('chat_id', chat_id);
const author = createAuthorHuman(0, 'User');
const text = 'Who are you?';
console.log('💬 User: ' + text);
characterAI.ask(author, chat_id, character_id, text, test_onstream);
}
test();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment