clevertype
Version:
An extensive Javascript/Typescript API wrapper for Cleverbot
348 lines (308 loc) • 13.3 kB
text/typescript
///<reference path="../index.d.ts"/>
import {APIResponse, ChatHistory, CleverbotState, Config, Mood} from 'clevertype';
import * as iconv from 'iconv-lite'
import axios, {AxiosError, AxiosInstance, AxiosResponse} from 'axios'
import {Exceptions} from "./Exceptions";
import moment = require("moment");
import {isString} from "util";
import {User} from "./User";
import * as https from "https";
export class Cleverbot {
private endpoint : string;
// the config file is our preset for the mood of subsequent calls
// when multi user is off
private config : Config = {
apiKey:'',
mood: {
emotion: 50,
engagement: 50,
regard: 50
}
};
private multiUser : boolean;
private _users ?: Map<string, User>;
private CleverbotState ?: CleverbotState;
private numberOfAPICalls : number;
private wrapperName : string;
private history ?: ChatHistory[];
private statusCodes : string[];
private instance: AxiosInstance;
constructor(input : string | Config, multiUser ?: boolean) {
if (typeof input === 'string')
this.config.apiKey = input;
else if (typeof input === 'object') {
this.config.apiKey = input.apiKey;
if (input.mood) {
// our default config mood is already 50 for each so we just pass
input.mood.emotion ? this.setEmotion(input.mood.emotion) : '';
input.mood.engagement ? this.setEngagement(input.mood.engagement) : '';
input.mood.regard ? this.setRegard(input.mood.regard) : '';
}
}
else {
throw new TypeError(`Cleverbot constructor expects either a string or an Config object.`);
}
if (multiUser) {
this.multiUser = true;
this._users = new Map<string, User>();
}
else { // we don't want this initialized if we don't need to
this.multiUser = false;
this.history = [];
}
this.endpoint= 'https://www.cleverbot.com/getreply?key=' + this.config.apiKey;
this.wrapperName = 'Clevertype';
this.numberOfAPICalls = 0;
this.statusCodes = Object.keys(Exceptions);
// the first cs request actually does return us a reply
this.instance = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
}
private get encodedWrapperName() : string {
return '&wrapper=' + this.wrapperName;
}
private encodedEmotion(emotion ?: number) : string {
if (emotion){
return '&cb_settings_tweak1=' + emotion;
}
else if (!this.config.mood || this.config.mood.emotion === undefined){
return '';
}
return '&cb_settings_tweak1=' + this.config.mood.emotion;
}
private encodedEngagement(engagement?: number) : string {
if (engagement){
return '&cb_settings_tweak2=' + engagement;
}
else if (!this.config.mood || this.config.mood.engagement === undefined){
return '';
}
return '&cb_settings_tweak2=' + this.config.mood.engagement;
}
private encodedRegard(regard?: number) : string {
if (regard){
return '&cb_settings_tweak3=' + regard;
}
else if (!this.config.mood || this.config.mood.regard === undefined){
return '';
}
return '&cb_settings_tweak3=' + this.config.mood.regard;
}
private encodedCleverbotState(state ?: string) : string {
if (state){
return '&cs=' + state;
}
if (this.CleverbotState === undefined ) return '';
return '&cs=' + this.CleverbotState;
}
private static encodeInput(input : string) : string {
const out = iconv.encode(input, 'utf-8');
return '&input=' + encodeURIComponent(out.toString());
}
private setCleverbotState(state : string, id?: string | number) : void {
if (this.multiUser && id){
const user : User = this.resolveUser(id);
user.cs = state
}
else {
this.CleverbotState = state;
}
}
private createSingleUserHistory(userInput: string, cleverbotResponse : string,requestDate: Date) : ChatHistory {
if (this.multiUser || !this.history)
throw new Error('Tried to create single user history in multiUser mode ');
const latestConversation = Math.max(...this.history.map((hs : ChatHistory) => hs.number));
let history = <ChatHistory> {};
// we don't want to just set the two moods
// equals because that passes it by reference
// and the mood is something that can change
history.mood = JSON.parse(JSON.stringify(this.config.mood));
history.number = !isFinite(latestConversation) ? 1 : latestConversation +1;
history.input = userInput;
history.response = cleverbotResponse;
history.responseDate= new Date();
history.requestDate = requestDate;
history.delay = moment(history.responseDate).diff(history.requestDate);
history.getConversation = () => [history.input, history.response];
return history;
}
private createMultiUserHistory(userInput: string, cleverbotResponse : string, id: string | number, requestDate: Date) : ChatHistory {
let _id;
typeof id === 'string' ? _id = id : _id = id.toString();
const user : User = this.resolveUser(_id, true);
const latestConversation = Math.max(...user.history.map((hs : ChatHistory) => hs.number));
let history = <ChatHistory> {};
// we don't want to just set the two moods
// equals because that passes it by reference
// and the mood is something that can change
history.mood = JSON.parse(JSON.stringify(user.mood));
history.number = !isFinite(latestConversation) ? 1 : latestConversation +1;
history.input = userInput;
history.response = cleverbotResponse;
history.responseDate= new Date();
history.requestDate = requestDate;
history.delay = moment(history.responseDate).diff(history.requestDate);
history.getConversation = () => [history.input, history.response];
return history;
}
private static createUser(id : string | number, eng?: number, emo ?: number, reg ?: number) : User {
const _id = isString(id) ? id : id.toString();
let mood = <Mood> {
engagement: eng || 50,
emotion: emo || 50,
regard: reg || 50
};
return new User(_id, undefined, mood);
}
private resolveUser(user : string | number, safe?: boolean) : User {
let id : string;
let resolvedUser : User | undefined ;
if (!this.multiUser || this._users === undefined)
throw new Error(`Tried resolving user in non-multi user mode.`);
if (typeof user === 'number'){
id = user.toString();
}
else if (typeof user === 'string') {
id = user;
}
else {
throw new TypeError(`Use must be a string or a number.`);
}
resolvedUser = this._users.get(id);
if (resolvedUser === undefined) {
if (safe) {
throw new ReferenceError(`User ${user} was not found`);
}
const engagement = this.config.mood.engagement;
const emotion = this.config.mood.emotion;
const regard = this.config.mood.regard;
resolvedUser = Cleverbot.createUser(id, engagement, emotion, regard);
this._users.set(id, resolvedUser);
}
return resolvedUser;
}
public say(message : string, user?: string | number) : Promise<string>{
const requestDate : Date = new Date();
let _user : User | undefined;
if (user){
_user = this.resolveUser(user);
}
let endpoint : string = this.endpoint;
endpoint += this.encodedWrapperName;
endpoint += Cleverbot.encodeInput(message);
if (_user){
endpoint += this.encodedCleverbotState(_user.cs);
endpoint += this.encodedEmotion(_user.mood.emotion);
endpoint += this.encodedEngagement(_user.mood.engagement);
endpoint += this.encodedRegard(_user.mood.regard);
}
else {
endpoint += this.encodedCleverbotState();
endpoint += this.encodedEmotion();
endpoint += this.encodedEngagement();
endpoint += this.encodedRegard();
}
return this.instance.get(endpoint).then((res:AxiosResponse<APIResponse>) => {
if (res.statusText && this.statusCodes.includes(res.statusText.toString())){
const errorMessage : string = Exceptions[res.statusText];
return Promise.reject(errorMessage);
}
this.numberOfAPICalls++;
const response : string = res.data.output;
if (_user){
this.setCleverbotState(res.data.cs, _user.id);
_user.history.push(this.createMultiUserHistory(message, response, _user.id, requestDate));
_user.messages++;
}
else if (this.history && !this.multiUser){
this.setCleverbotState(res.data.cs);
this.history.push(this.createSingleUserHistory(message, response, requestDate))
}
return Promise.resolve(response);
}).catch((err:AxiosError)=> {
console.log('Error getting response from cleverbot\n' + err);
console.log('endpoint: ' + endpoint + '\n');
return Promise.reject(err);
});
}
public setEmotion(amount : number, user?: number | string) : void {
if (amount < 0 || amount > 100) throw new RangeError(`Emotion must be a value between 0 and 100.`);
else if (!user && !this.multiUser)
this.config.mood.emotion = amount;
else if (!user && this.multiUser)
throw new Error(`setEmotion requires a user id when it's in multi user mode`);
else if (user && !this.multiUser)
throw new Error(`Can not set emotion without user id when in multi user mode.`);
else if (user){
const resolved : User = this.resolveUser(user, true);
resolved.mood.emotion = amount;
}
}
public setEngagement(amount : number, user ?: number | string) : void {
if (amount < 0 || amount > 100) throw new RangeError(`Engagement must be a value between 0 and 100.`);
else if (!user && !this.multiUser)
this.config.mood.engagement = amount;
else if (!user && this.multiUser)
throw new Error(`setEngagement requires a user id when it's in multi user mode`);
else if (user && !this.multiUser)
throw new Error(`Can not set engagement without user id when in multi user mode.`);
else if (user){
const resolved : User = this.resolveUser(user, true);
resolved.mood.engagement = amount;
}
}
public setRegard(amount : number, user ?: number | string) : void {
if (amount < 0 || amount > 100) throw new RangeError(`Regard must be a value between 0 and 100.`);
else if (!user && !this.multiUser)
this.config.mood.regard = amount;
else if (!user && this.multiUser)
throw new Error(`setRegard requires a user id when it's in multi user mode`);
else if (user && !this.multiUser)
throw new Error(`Can not set regard without user id when in multi user mode.`);
else if (user){
const resolved : User = this.resolveUser(user, true);
resolved.mood.regard = amount;
}
}
public get callAmount() : number{
return this.numberOfAPICalls;
}
// for backwards compatibility
/**
* @deprecated deprecated method since 2.0.0, use getMood() instead
*/
public get mood(){
console.warn(`mood is deprecated, use getMood() instead.`);
return this.config.mood;
}
public getMood(user ?: string | number) : Mood {
if (!user && !this.multiUser)
return this.config.mood;
else if (user && !this.multiUser)
throw new Error(`Can not fetch user mood when not in multiUser mood`);
else if (user && this.multiUser)
return this.resolveUser(user, true).mood;
else
throw new Error(`A user id is required when fetching mood in multiUser mode`);
}
public get apiKey() : string {
return this.config.apiKey;
}
public get users() : User[] {
if (!this.multiUser || !this._users)
throw new Error(`Tried to fetch users but clevertype is not in multi user mode.`);
return Array.from(this._users.values());
}
public getUser(user: string | number){
return this.resolveUser(user, true);
}
public getHistory(user?: string | number){
if (user)
return this.resolveUser(user, true).history;
else
return this.history;
}
}