├── .gitignore ├── wolfram.py ├── google.py ├── wiki.py ├── README.md ├── chatterbotapi.py └── bot.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | -------------------------------------------------------------------------------- /wolfram.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | import requests 3 | 4 | def wolfram(query): 5 | appid='xxxxx' 6 | results = requests.get("http://api.wolframalpha.com/v2/query", params={'input':query,'appid': appid}, headers={'User-Agent': "Mozilla"}) 7 | results = results.text 8 | results=results.encode('utf-8') 9 | root=ET.fromstring(results) 10 | reply=None 11 | print "querying wolfram for "+query 12 | for pod in root.findall('pod'): 13 | if (pod.attrib['id']=="Result"): 14 | reply=pod[0][0].text 15 | print reply 16 | if not reply: 17 | reply="noidea" 18 | return reply 19 | -------------------------------------------------------------------------------- /google.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import json as m_json 3 | 4 | def google(terms): # google 5 | '''Returns the link and the description of the first result from a google 6 | search 7 | ''' 8 | #query = raw_input ( 'Query: ' ) 9 | query=terms 10 | print "going to google %s" % query 11 | query = urllib.urlencode ( { 'q' : query } ) 12 | response = urllib.urlopen ( 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&' + query ).read() 13 | json = m_json.loads ( response ) 14 | results = json [ 'responseData' ] [ 'results' ] 15 | returnval="" 16 | for result in results: 17 | title = result['title'] 18 | url = result['url'] # was URL in the original and that threw a name error exception 19 | #print ( title + '; ' + url ) 20 | title=title.translate({ord(k):None for k in u''}) 21 | title=title.translate({ord(k):None for k in u''}) 22 | returnval += title + ' ; ' + url + '\n' 23 | 24 | print "returning %s" %returnval 25 | return returnval.encode('utf-8') 26 | -------------------------------------------------------------------------------- /wiki.py: -------------------------------------------------------------------------------- 1 | from BeautifulSoup import BeautifulSoup 2 | import urllib2 3 | 4 | def wiki(term): # wiki 5 | 'Returns a wiki link and the first paragraph of the page' 6 | 7 | main_page = 'http://en.wikipedia.org/wiki/Main_Page' 8 | print "Going to fetch wiki of %s" % term 9 | wlink = term # notice the trailing space 10 | if 1 == len(wlink): # no search term given, the Main_Page is "displayed" 11 | response = main_page 12 | else: 13 | #search_term = wlink[1].lstrip().replace(' ', '_') 14 | search_term = wlink.replace(' ', '_') 15 | #print search_term 16 | 17 | if len(search_term) < 1: 18 | response = main_page 19 | else: 20 | response = 'http://en.wikipedia.org/wiki/' + search_term 21 | 22 | response = response + ' ' + get_para(response) 23 | 24 | return response.encode('utf-8') 25 | 26 | def get_para(wlink): 27 | 'Gets the first paragraph from a wiki link' 28 | 29 | msg = '' 30 | try: 31 | page_request = urllib2.Request(wlink) 32 | page_request.add_header('User-agent', 'Mozilla/5.0') 33 | page = urllib2.urlopen(page_request) 34 | except IOError: 35 | msg = 'Cannot acces link!' 36 | else: 37 | 38 | soup = BeautifulSoup(page) 39 | msg = ''.join(soup.find('div', { 'id' : 'bodyContent'}).p.findAll(text=True)) 40 | 41 | while 460 < len(msg): # the paragraph cannot be longer than 510 42 | # characters including the protocol command 43 | pos = msg.rfind('.') 44 | msg = msg[:pos] 45 | 46 | return msg 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python-telegram-bot 2 | =================== 3 | 4 | This is a telegram bot written in python. It uses the CLI of telegram by vysheng to connect. 5 | 6 | ## Note: There is a better bot ## 7 | This python based bot was written when I did not know how to use lua (which is embedded in tg-cli) to extend tg-cli. 8 | 9 | But user yagop came up with [a better bot written in lua](https://github.com/yagop/telegram-bot) 10 | 11 | I will be submitting my improvements to his repo. If you want to keep up with my own fork, [check this out](https://github.com/asdofindia/telegram-bot) 12 | 13 | Since Lua is a lovable language and sicne tg-cli can pass it structured arguments, I will be trying to develop pure Lua modules in the lua bot rather than continue developing in python in this bot here. 14 | 15 | Also, currently the way this bot operates is by reading lines from tg-cli output and parsing who sent it, to which group, etc based on the colours of the output. This is very unreliable and buggy and therefore not the preferred way of doing things. Although it works now, it could break any time. So it is wiser to follow the Lua version. 16 | 17 | If you still want to use this bot, continue reading. 18 | 19 | 20 | ## Installation ## 21 | 22 | ### Install and setup vysheng/tg ### 23 | Install [vysheng's tg cli](http://github.com/vysheng/tg/#installation) 24 | 25 | Run that and configure an account 26 | 27 | ./telegram -k tg-server.pub 28 | 29 | ### Install dependencies ### 30 | The twitter module requires [Python Twitter Tools](http://mike.verdone.ca/twitter/) 31 | 32 | sudo easy_install twitter 33 | twitter authorize 34 | 35 | Install BeautifulSoup and other python modules with easy_install 36 | 37 | sudo easy_install BeautifulSoup 38 | 39 | ### Download python-telegram-bot ### 40 | Clone this repository by doing 41 | 42 | git clone https://github.com/asdofindia/python-telegram-bot.git && cd python-telegram-bot 43 | 44 | ### Configure python-telegram-bot ### 45 | There are a few changes to be made to the source code before it can start working. 46 | 47 | In bot.py, change pathtotg to the path to the vysheng's tg CLI 48 | In bot.py, in the twitter function, change groupeer to the telegram user to whom you want the bot to send a message when there's a new tweet. 49 | In bot.py, in the twitter function, change the follow array to the accounts you want to get updates from 50 | In wolfram.py, put an appid by signing up at wolfram developers 51 | 52 | ### Disabling modules ### 53 | If you don't want some of these modules, just remove them from the modules array in callmodule function 54 | If you want to disable twitter, just comment out all lines with 'twitbot' in it in the main function. 55 | If you want to disable robotic replies turn chattybot to False 56 | 57 | ## Features ## 58 | The features as of now are 59 | 60 | * can be invoked from within a group or a direct message 61 | * wiki "search terms" will return the first paragraph of the wikipedia article 62 | * google "search terms" will return a few google results 63 | * bot "question" will fetch the answer from wolfram alpha api 64 | * new twitter messages from your feed are automatically sent to the defined peer 65 | * talks random shit based on pandorabots web service thanks to chatterbotapi 66 | 67 | ## Known Bugs ## 68 | 69 | * twitter module repeats tweets. This is more of a problem with the twitter python module rather than this code. Todo: prevent duplicate tweets using timestamp 70 | * chatting with oneself is buggy, I suppose. Not tested very much. No time :P 71 | -------------------------------------------------------------------------------- /chatterbotapi.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import urllib 3 | import urllib2 4 | import uuid 5 | import xml.dom.minidom 6 | 7 | """ 8 | chatterbotapi 9 | Copyright (C) 2011 pierredavidbelanger@gmail.com 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU Lesser General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Lesser General Public License for more details. 18 | You should have received a copy of the GNU Lesser General Public License 19 | along with this program. If not, see . 20 | """ 21 | 22 | ################################################# 23 | # API 24 | ################################################# 25 | 26 | class ChatterBotType: 27 | 28 | CLEVERBOT = 1 29 | JABBERWACKY = 2 30 | PANDORABOTS = 3 31 | 32 | class ChatterBotFactory: 33 | 34 | def create(self, type, arg = None): 35 | if type == ChatterBotType.CLEVERBOT: 36 | return _Cleverbot('http://www.cleverbot.com/webservicemin', 35) 37 | elif type == ChatterBotType.JABBERWACKY: 38 | return _Cleverbot('http://jabberwacky.com/webservicemin', 29) 39 | elif type == ChatterBotType.PANDORABOTS: 40 | if arg == None: 41 | raise Exception('PANDORABOTS needs a botid arg') 42 | return _Pandorabots(arg) 43 | return None 44 | 45 | class ChatterBot: 46 | 47 | def create_session(self): 48 | return None 49 | 50 | class ChatterBotSession: 51 | 52 | def think_thought(self, thought): 53 | return thought 54 | 55 | def think(self, text): 56 | thought = ChatterBotThought() 57 | thought.text = text 58 | return self.think_thought(thought).text 59 | 60 | class ChatterBotThought: 61 | 62 | pass 63 | 64 | ################################################# 65 | # Cleverbot impl 66 | ################################################# 67 | 68 | class _Cleverbot(ChatterBot): 69 | 70 | def __init__(self, url, endIndex): 71 | self.url = url 72 | self.endIndex = endIndex 73 | 74 | def create_session(self): 75 | return _CleverbotSession(self) 76 | 77 | class _CleverbotSession(ChatterBotSession): 78 | 79 | def __init__(self, bot): 80 | self.bot = bot 81 | self.vars = {} 82 | self.vars['start'] = 'y' 83 | self.vars['icognoid'] = 'wsf' 84 | self.vars['fno'] = '0' 85 | self.vars['sub'] = 'Say' 86 | self.vars['islearning'] = '1' 87 | self.vars['cleanslate'] = 'false' 88 | 89 | def think_thought(self, thought): 90 | self.vars['stimulus'] = thought.text 91 | data = urllib.urlencode(self.vars) 92 | data_to_digest = data[9:self.bot.endIndex] 93 | data_digest = hashlib.new(data_to_digest).hexdigest() 94 | data = data + '&icognocheck=' + data_digest 95 | url_response = urllib2.urlopen(self.bot.url, data) 96 | response = url_response.read() 97 | response_values = response.split('\r') 98 | #self.vars['??'] = _utils_string_at_index(response_values, 0) 99 | self.vars['sessionid'] = _utils_string_at_index(response_values, 1) 100 | self.vars['logurl'] = _utils_string_at_index(response_values, 2) 101 | self.vars['vText8'] = _utils_string_at_index(response_values, 3) 102 | self.vars['vText7'] = _utils_string_at_index(response_values, 4) 103 | self.vars['vText6'] = _utils_string_at_index(response_values, 5) 104 | self.vars['vText5'] = _utils_string_at_index(response_values, 6) 105 | self.vars['vText4'] = _utils_string_at_index(response_values, 7) 106 | self.vars['vText3'] = _utils_string_at_index(response_values, 8) 107 | self.vars['vText2'] = _utils_string_at_index(response_values, 9) 108 | self.vars['prevref'] = _utils_string_at_index(response_values, 10) 109 | #self.vars['??'] = _utils_string_at_index(response_values, 11) 110 | self.vars['emotionalhistory'] = _utils_string_at_index(response_values, 12) 111 | self.vars['ttsLocMP3'] = _utils_string_at_index(response_values, 13) 112 | self.vars['ttsLocTXT'] = _utils_string_at_index(response_values, 14) 113 | self.vars['ttsLocTXT3'] = _utils_string_at_index(response_values, 15) 114 | self.vars['ttsText'] = _utils_string_at_index(response_values, 16) 115 | self.vars['lineRef'] = _utils_string_at_index(response_values, 17) 116 | self.vars['lineURL'] = _utils_string_at_index(response_values, 18) 117 | self.vars['linePOST'] = _utils_string_at_index(response_values, 19) 118 | self.vars['lineChoices'] = _utils_string_at_index(response_values, 20) 119 | self.vars['lineChoicesAbbrev'] = _utils_string_at_index(response_values, 21) 120 | self.vars['typingData'] = _utils_string_at_index(response_values, 22) 121 | self.vars['divert'] = _utils_string_at_index(response_values, 23) 122 | response_thought = ChatterBotThought() 123 | response_thought.text = _utils_string_at_index(response_values, 16) 124 | return response_thought 125 | 126 | ################################################# 127 | # Pandorabots impl 128 | ################################################# 129 | 130 | class _Pandorabots(ChatterBot): 131 | 132 | def __init__(self, botid): 133 | self.botid = botid 134 | 135 | def create_session(self): 136 | return _PandorabotsSession(self) 137 | 138 | class _PandorabotsSession(ChatterBotSession): 139 | 140 | def __init__(self, bot): 141 | self.vars = {} 142 | self.vars['botid'] = bot.botid 143 | self.vars['custid'] = uuid.uuid1() 144 | 145 | def think_thought(self, thought): 146 | self.vars['input'] = thought.text 147 | data = urllib.urlencode(self.vars) 148 | url_response = urllib2.urlopen('http://www.pandorabots.com/pandora/talk-xml', data) 149 | response = url_response.read() 150 | response_dom = xml.dom.minidom.parseString(response) 151 | response_thought = ChatterBotThought() 152 | response_thought.text = response_dom.getElementsByTagName('that')[0].childNodes[0].data.strip() 153 | return response_thought 154 | 155 | ################################################# 156 | # Utils 157 | ################################################# 158 | 159 | def _utils_string_at_index(strings, index): 160 | if len(strings) > index: 161 | return strings[index] 162 | else: 163 | return '' 164 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import re 3 | from wiki import wiki 4 | from google import google 5 | import lxml.html 6 | from wolfram import wolfram 7 | from threading import Thread 8 | import time 9 | 10 | from BeautifulSoup import BeautifulSoup 11 | from chatterbotapi import ChatterBotFactory, ChatterBotType 12 | 13 | 14 | pathtotg='~/tg/' #include trailing slash. I don't know if '~' notation works 15 | lastmessage=None 16 | proc=None 17 | chattybot=True 18 | 19 | 20 | factory = ChatterBotFactory() 21 | bot1 = factory.create(ChatterBotType.PANDORABOTS, 'b0dafd24ee35a477') 22 | botsessions={} 23 | 24 | #this function somehow works in preventing duplicate messages 25 | def mymessage(message): 26 | global lastmessage 27 | if (message==lastmessage): 28 | return True 29 | else: 30 | return False 31 | 32 | def callmodule(message,peer): 33 | message=message.lower() 34 | modules=["wiki","bot","google"] #Add module name here so that the for loop below works 35 | for module in modules: 36 | if (message.find(module)==0): 37 | message=message[len(module)+1:] 38 | if module=="wiki": 39 | reply=wiki(message) 40 | if ("Cannot acces link!" in reply): 41 | reply="No wikipedia article on that, googling instead\n"+google(message) 42 | return reply 43 | if module=="google": 44 | return google(message) 45 | if module=="bot": 46 | message=message.lstrip() 47 | reply=wolfram(message) 48 | if (reply=="noidea"): 49 | reply="tough. I'll google that\n"+google(message) 50 | return reply 51 | global chattybot 52 | if chattybot: 53 | global botsessions 54 | global bot1 55 | if peer not in botsessions: 56 | botsessions[peer]=bot1.create_session() 57 | reply = botsessions[peer].think(message) 58 | VALID_TAGS = ['br'] 59 | soup = BeautifulSoup(reply) 60 | for tag in soup.findAll(True): 61 | if tag.name not in VALID_TAGS: 62 | tag.hidden = True 63 | reply=soup.renderContents() 64 | reply=reply.replace('
','\n') 65 | return reply 66 | 67 | def AI(group,peer,message): 68 | #uncomment for debug 69 | #if lastmessage is not None: 70 | # print 'message is '+message+' and lastmessage is '+lastmessage+'\n' 71 | if mymessage(message): 72 | return 73 | replyrequired=False 74 | reply=None 75 | if group is None: 76 | replyrequired=True 77 | if (message[:3].lower()=="bot"): 78 | replyrequired=True 79 | reply=callmodule(message,peer) 80 | if((message[:4]=="http") and (message.find(' ')==-1)): 81 | t = lxml.html.parse(message) 82 | reply = t.find(".//title").text 83 | #if ((replyrequired==True) and (reply is None)): 84 | #reply="underconstruction" 85 | if reply is not None: 86 | msg(group,peer,reply) 87 | 88 | def spam(message): 89 | if (message == lastmessage): 90 | return True 91 | else: 92 | return False 93 | 94 | 95 | def msg(group,peer,message): 96 | global proc 97 | if (group is not None): 98 | message=peer.split(' ')[0] + ": " + message 99 | peer=group 100 | if (not spam(message)): 101 | if(('\n' in message)or('\r' in message) or ('\r\n' in message)): 102 | tempfile='temp' 103 | temp=open(tempfile,'w') 104 | temp.write(message) 105 | temp.close() 106 | proc.stdin.write('send_text '+peer.replace(' ','_')+' '+tempfile+'\n') 107 | else: 108 | proc.stdin.write('msg '+peer.replace(' ','_')+' '+message+'\n') 109 | global lastmessage 110 | lastmessage=message 111 | 112 | def bot(): 113 | 114 | COLOR_RED="\033[0;31m" 115 | COLOR_REDB="\033[1;31m" 116 | COLOR_NORMAL="\033[0m" 117 | COLOR_GREEN="\033[32;1m" 118 | COLOR_GREY="\033[37;1m" 119 | COLOR_YELLOW="\033[33;1m" 120 | COLOR_BLUE="\033[34;1m" 121 | COLOR_MAGENTA="\033[35;1m" 122 | COLOR_CYAN="\033[36;1m" 123 | COLOR_LCYAN="\033[0;36m" 124 | COLOR_INVERSE="\033[7m" 125 | 126 | global pathtotg 127 | global proc 128 | proc=subprocess.Popen([pathtotg+'bin/telegram-cli','-k',pathtotg+'tg-server.pub'],stdin=subprocess.PIPE,stdout=subprocess.PIPE) 129 | lastmessage=None 130 | multiline=False 131 | for line in iter(proc.stdout.readline,''): 132 | if multiline: 133 | message+=line 134 | if line.endswith('[0m\n'): 135 | message=message.rstrip('[0m\n') 136 | multiline=False 137 | else: 138 | if ((COLOR_YELLOW+" is now online" in line) or (COLOR_YELLOW+" is now offline" in line) or (COLOR_YELLOW+": 0 unread" in line)): 139 | pass 140 | print line.rstrip() 141 | with open('output','a') as fil: 142 | fil.write(line) 143 | group=None 144 | peer=None 145 | message=None 146 | try: 147 | if ((COLOR_BLUE+" >>>" in line) and (COLOR_BLUE+"[" in line)): 148 | #if you get change colour level errors, uncomment the below line, and comment the line below that. 149 | #peer=line.split(COLOR_REDB)[1].split(COLOR_RED)[0] 150 | peer=line.split(COLOR_RED)[1].split(COLOR_NORMAL)[0] 151 | message=line.split(COLOR_BLUE+" >>> ")[1].split("\033")[0] 152 | if not line.endswith("[0m\n"): 153 | multiline=True 154 | if ((COLOR_GREEN+" >>>" in line)): 155 | group=line.split(COLOR_MAGENTA)[2].split(COLOR_NORMAL)[0] 156 | #For change colour level 157 | #peer=line.split(COLOR_REDB)[1].split(COLOR_RED)[0] 158 | peer=line.split(COLOR_RED)[1].split(COLOR_NORMAL)[0] 159 | message=line.split(COLOR_GREEN+" >>> ")[1].strip(COLOR_NORMAL).split("\033")[0] 160 | if not line.endswith("[0m\n"): 161 | multiline=True 162 | if ((COLOR_BLUE+" >>>" in line) and (COLOR_MAGENTA+"[" in line)): 163 | group=line.split(COLOR_MAGENTA)[2].split(COLOR_NORMAL)[0] 164 | #for change colour level 165 | #peer=line.split(COLOR_REDB)[1].split(COLOR_RED)[0] 166 | peer=line.split(COLOR_RED)[1].split(COLOR_NORMAL)[0] 167 | message=line.split(COLOR_BLUE+" >>> ")[1].strip(COLOR_NORMAL).split("\033")[0] 168 | if not line.endswith("[0m\n"): 169 | multiline=True 170 | except IndexError: 171 | print "Error: Change colour levels" 172 | if( ((group is not None) or (peer is not None)) and (message is not None) and (not multiline)): 173 | AI(group,peer,message) 174 | 175 | 176 | #for twitter to work you need to 'sudo easy_install twitter' 177 | #after that, choose a recepient by changing groupeer below 178 | def twitter(): 179 | #change the group/peer name here to choose where the tweets are sent to 180 | groupeer="Akshay S Dinesh" 181 | time.sleep(10) 182 | tweets=subprocess.Popen(['twitter','-r'],stdin=subprocess.PIPE,stdout=subprocess.PIPE) 183 | for tweet in iter(tweets.stdout.readline,''): 184 | try: 185 | author,tweet=tweet.split(' ',1) 186 | except ValueError: 187 | continue 188 | tweet=tweet.replace('\n','') 189 | follow=["TheHindu","premierleague","WHO","IPL"] 190 | if (author in follow): 191 | msg(groupeer,"@"+author,tweet) 192 | else: 193 | pass 194 | time.sleep(1) 195 | 196 | 197 | #this doesn't work for now 198 | def timeresponse(): 199 | while True: 200 | if time.strftime("%H")=="00": 201 | print "time is 12 o'clock" 202 | 203 | 204 | def main(): 205 | botthread = Thread(target = bot) 206 | botthread.start() 207 | 208 | twitbot = Thread(target=twitter) 209 | twitbot.start() 210 | 211 | botthread.join() 212 | twitbot.join() 213 | 214 | if __name__ == "__main__": 215 | main() 216 | --------------------------------------------------------------------------------