diff --git a/cleverbot3.py b/cleverbot3.py new file mode 100644 index 000000000..103c9e113 --- /dev/null +++ b/cleverbot3.py @@ -0,0 +1,176 @@ +"""Python library allowing interaction with the Cleverbot API.""" +import http.cookiejar +import hashlib +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +from urllib.parse import unquote +import html + + +class Cleverbot: + """ + Wrapper over the Cleverbot API. + + """ + HOST = "www.cleverbot.com" + PROTOCOL = "http://" + RESOURCE = "/webservicemin" + API_URL = PROTOCOL + HOST + RESOURCE + + headers = { + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)', + 'Accept': 'text/html,application/xhtml+xml,' + 'application/xml;q=0.9,*/*;q=0.8', + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'Accept-Language': 'en-us,en;q=0.8,en-us;q=0.5,en;q=0.3', + 'Cache-Control': 'no-cache', + 'Host': HOST, + 'Referer': PROTOCOL + HOST + '/', + 'Pragma': 'no-cache' + } + + def __init__(self): + """ The data that will get passed to Cleverbot's web API """ + self.data = { + 'stimulus': '', + 'start': 'y', # Never modified + 'sessionid': '', + 'vText8': '', + 'vText7': '', + 'vText6': '', + 'vText5': '', + 'vText4': '', + 'vText3': '', + 'vText2': '', + 'icognoid': 'wsf', # Never modified + 'icognocheck': '', + 'fno': 0, # Never modified + 'prevref': '', + 'emotionaloutput': '', # Never modified + 'emotionalhistory': '', # Never modified + 'asbotname': '', # Never modified + 'ttsvoice': '', # Never modified + 'typing': '', # Never modified + 'lineref': '', + 'sub': 'Say', # Never modified + 'islearning': 1, # Never modified + 'cleanslate': False, # Never modified + } + + # the log of our conversation with Cleverbot + self.conversation = [] + self.resp = str() + + # install an opener with support for cookies + cookies = http.cookiejar.LWPCookieJar() + handlers = [ + urllib.request.HTTPHandler(), + urllib.request.HTTPSHandler(), + urllib.request.HTTPCookieProcessor(cookies) + ] + opener = urllib.request.build_opener(*handlers) + urllib.request.install_opener(opener) + + # get the main page to get a cookie (see bug #13) + try: + urllib.request.urlopen(Cleverbot.PROTOCOL + Cleverbot.HOST) + except urllib.error.HTTPError: + # TODO errors shouldn't pass unnoticed, + # here and in other places as well + return str() + + def ask(self, question): + """Asks Cleverbot a question. + + Maintains message history. + + Args: + q (str): The question to ask + + Returns: + Cleverbot's answer + """ + # Set the current question + self.data['stimulus'] = question + + # Connect to Cleverbot's API and remember the response + try: + self.resp = self._send() + except urllib.error.HTTPError: + # request failed. returning empty string + return str() + + # Add the current question to the conversation log + self.conversation.append(question) + + parsed = self._parse() + + # Set data as appropriate + if self.data['sessionid'] != '': + self.data['sessionid'] = parsed['conversation_id'] + + # Add Cleverbot's reply to the conversation log + self.conversation.append(parsed['answer']) + + return html.unescape(parsed['answer']) + + def _send(self): + """POST the user's question and all required information to the + Cleverbot API + + Cleverbot tries to prevent unauthorized access to its API by + obfuscating how it generates the 'icognocheck' token, so we have + to URLencode the data twice: once to generate the token, and + twice to add the token to the data we're sending to Cleverbot. + """ + # Set data as appropriate + if self.conversation: + linecount = 1 + for line in reversed(self.conversation): + linecount += 1 + self.data['vText' + str(linecount)] = line + if linecount == 8: + break + + # Generate the token + enc_data = urllib.parse.urlencode(self.data) + digest_txt = enc_data[9:35] + digest_txt = bytearray(digest_txt, 'utf-8') + token = hashlib.md5(digest_txt).hexdigest() + self.data['icognocheck'] = token + + # Add the token to the data + enc_data = urllib.parse.urlencode(self.data) + enc_data = bytearray(enc_data, 'utf-8') + req = urllib.request.Request(self.API_URL, enc_data, self.headers) + + # POST the data to Cleverbot's API + conn = urllib.request.urlopen(req) + resp = conn.read() + + # Return Cleverbot's response + return resp + + def _parse(self): + """Parses Cleverbot's response""" + resp = self.resp.decode('utf-8') + parsed = [ + item.split('\r') for item in resp.split('\r\r\r\r\r\r')[:-1] + ] + try: + parsed_dict = { + 'answer': parsed[0][0], + 'conversation_id': parsed[0][1], + 'conversation_log_id': parsed[0][2], + } + except: + parsed_dict = { + 'answer': parsed[0][0], + 'conversation_id': parsed[0][1], + 'conversation_log_id': 'not found', + } + try: + parsed_dict['unknown'] = parsed[1][-1] + except IndexError: + parsed_dict['unknown'] = None + return parsed_dict diff --git a/red.py b/red.py index da7226d25..8ab1901dd 100644 --- a/red.py +++ b/red.py @@ -16,6 +16,7 @@ import aiohttp import traceback import re import youtube_dl +import cleverbot3 import os import asyncio import glob @@ -44,6 +45,7 @@ help = """**Commands list:** !roll [number] - Random number between 0 and [number] !gif [text] - GIF search !urban [text] - Search definitions in the urban dictionary +!chat [text] - Chat with the Cleverbot !customcommands - Custom commands' list !addcom [command] [text] - Add a custom command !editcom [command] [text] - Edit a custom command @@ -202,6 +204,8 @@ async def on_message(message): await gif(message) elif message.content.startswith('!urban'): await urban(message) + elif message.content.startswith('!chat'): + await chat(message) elif message.content.startswith('!uptime'): await uptime(message) elif message.content.startswith('!avatar'): @@ -844,6 +848,21 @@ async def urban(message): else: await client.send_message(message.channel, "!urban [text]") +async def chat(message): + msg = message.content.split() + if len(msg) > 1: + try: + msg.remove(msg[0]) + msg = "+".join(msg) + question = msg + answer = cleverbot_client.ask(question) + if msg != "": + await client.send_message(message.channel, "{}: ".format(message.author.mention) + answer) + except: + await client.send_message(message.channel, "Error.") + else: + await client.send_message(message.channel, "!chat [text]") + async def gif(message): msg = message.content.split() if len(msg) > 1: