UNPKG

clevertype

Version:

An extensive Javascript/Typescript API wrapper for Cleverbot

348 lines (308 loc) 13.3 kB
///<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; } }