def _reply(self, irc, msg, channel, text): """Send a response to text""" cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) for i in range(response.lower().count(self.magicnick.lower())): # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace(self.magicnick, random.choice(list(irc.state.channels[msg.args[0]].users))) if self.magicnick.lower() in response: response = response.replace(self.magicnick.lower(), random.choice(list(irc.state.channels[msg.args[0]].users))) cobeBrain.learn(response) # Let's have the bot learn the wacky things it says self.log.info("Attempting to respond in {0} with message: {1}".format(channel, response)) # delay the response here so we look real? if self.registryValue('responseDelay', channel): self.log.info("Delayed the response in %s." % channel) delayseconds = time.time() + random.randint(2, 5) schedule.addEvent(irc.queueMsg(ircmsgs.privmsg(channel, response)), delayseconds) else: irc.queueMsg(ircmsgs.privmsg(channel, response))
def _learn(self, irc, channel, text, probability): """Internal method for learning phrases.""" if os.path.exists(self._getBrainDirectoryForChannel(channel)): # Does this channel have a directory for the brain file stored and does this file exist? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): self.log.debug("Learning: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) if random.randint(0, 10000) <= probability: self._reply(irc, channel, text) else: # Nope, let's make it! commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): self.log.debug("Learning: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) if random.randint(0, 10000) <= probability: self._reply(irc, channel, text)
def learn_text(text, brain_name): brain = Brain(brain_name) if not os.path.isfile(brain_name): print("- Training...") for sent in text: brain.learn(sent) return brain
def _learn(self, irc, msg, channel, text, probability): """Internal method for learning phrases.""" if os.path.exists(self._getBrainDirectoryForChannel(channel)): # Does this channel have a directory for the brain file stored and does this file exist? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): self.log.debug("Learning: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) if random.randint(0, 10000) <= probability: self._reply(irc, msg, channel, text) else: # Nope, let's make it! commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): self.log.debug("Learning: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) if random.randint(0, 10000) <= probability: self._reply(irc, msg, channel, text)
def _reply(self, irc, msg, channel, text): """Send a response to text""" cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) for i in range(response.lower().count(self.magicnick.lower())): # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace( self.magicnick, random.choice(list(irc.state.channels[msg.args[0]].users))) if self.magicnick.lower() in response: response = response.replace( self.magicnick.lower(), random.choice(list(irc.state.channels[msg.args[0]].users))) cobeBrain.learn( response) # Let's have the bot learn the wacky things it says self.log.info("Attempting to respond in {0} with message: {1}".format( channel, response)) # delay the response here so we look real? if self.registryValue('responseDelay', channel): self.log.info("Delayed the response in %s." % channel) delayseconds = time.time() + random.randint(2, 5) schedule.addEvent(irc.queueMsg(ircmsgs.privmsg(channel, response)), delayseconds) else: irc.queueMsg(ircmsgs.privmsg(channel, response))
class Markov: def __init__(self, brain_id): self.brain_id = brain_id brain_path = f'data/cobe/{brain_id}' os.makedirs('data/cobe/', exist_ok=True) self.brain = Brain(brain_path) def filter(self, message): message = re.sub('\<@[A-Z0-9a-z]{9}\>', '', message) # remove mentions message = re.sub('\s{2,}', ' ', message) #remove double spaces message = re.sub('\<[^\<]+\>', '', message) #remove shit like links message = message.strip() # remove unneeded spaces valid = False if len(message) > 5: valid = True return [valid, message] def learn(self, message): valid, message = self.filter(message) if not valid: return self.brain.learn(message) def speak(self, message): response = self.brain.reply(message) valid, response = self.filter(response) if not valid: return None return response
def handle(msg): print msg content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': brain = Brain(config.get('Brain', 'path') + str(chat_id) + ".brain") brain.learn(msg['text']) if 'braulio' in msg['text'].lower(): bot.sendMessage(chat_id,brain.reply(msg['text']))
def handle(msg): print msg content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': brain = Brain(config.get('Brain', 'path') + str(chat_id) + ".brain") brain.learn(msg['text']) if 'braulio' in msg['text'].lower(): bot.sendMessage(chat_id, brain.reply(msg['text']))
def handle(msg): content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': brain = Brain(config.get('Brain', 'path') + str(chat_id) + ".brain") brain.learn(msg['text']) if 'reply_to_message' in msg and msg['reply_to_message']['from']['username'] == "Braulio_bot": bot.sendMessage(chat_id,brain.reply(msg['text']),reply_to_message_id=msg['message_id']) elif 'braulio' in msg['text'].lower(): bot.sendMessage(chat_id,brain.reply(msg['text']).replace("Braulio",msg['from']['first_name']))
def _learn_corpus(self, corpus_file, brain_name, questions=False): if not os.path.isfile(brain_name): brain = Brain(brain_name) print("- Training...") corpus = read_file(corpus_file) corpus = clean_text(corpus, get_questions=questions) for sent in corpus: brain.learn(sent) return Brain(brain_name)
def _learn(self, irc, channel, text, probability): text = self._cleantext(text) if text: if len(text) > 0 and not text.isspace(): b = Brain(self._brainfile) b.learn(text) # determine probability to respond. if random.randint(0, 100) < probability: # if we've randomly determined to talk, check the update time. sentinel = self._updatesentinel(channel) if sentinel: self._reply(irc, channel, text)
def corpuslearn(self, irc, msg, args, text): """<text> Manually teach the corpus <text> """ text = self._cleantext(text) if text and len(text) > 1 and not text.isspace(): irc.reply("I am learning: {0}".format(text)) b = Brain(self._brainfile) b.learn(text) else: irc.reply("After sanitizing, I did not have any text to learn.")
def fun(message): print message.text brain = Brain("/db/" + str(message.chat.id)[1:] + ".br") # Telegram understands UTF-8, so encode text for unicode compatibility brain.learn(message.text) if "tagueul" in message.text.lower() or "tg" in message.text.lower() or "ta gueule" in message.text.lower(): bot.reply_to(message, "Non, toi ta gueule.") elif (randint(1, 100) < percent): if violence : bot.reply_to(message, brain.reply(message.text.upper(), 3000)) else : bot.reply_to(message, brain.reply(message.text, 3000)) return 'ok'
def testLearnStems(self): Brain.init(TEST_BRAIN_FILE, order=2) brain = Brain(TEST_BRAIN_FILE) brain.set_stemmer("english") stem = brain.stemmer.stem brain.learn("this is testing") c = brain.graph.cursor() stem_count = c.execute("SELECT count(*) FROM token_stems").fetchone() self.assertEqual(3, stem_count[0]) self.assertEqual(brain.graph.get_token_stem_id(stem("test")), brain.graph.get_token_stem_id(stem("testing")))
class Chatter(object): def __init__(self): self.brain = Brain('cobe.brain') with open('seed.txt', 'r') as f: text = [l.strip() for l in f.read().replace('\n', ' ').replace('.', '\n').replace('?', '\n').replace('\xa0', ' - ').split('\n') if l.strip()] for line in text: self.brain.learn(line) def reply(self, message): return self.brain.reply(message) def learn(self, line): self.brain.learn(line)
def handle(msg): content_type, chat_type, chat_id = telepot.glance(msg) if content_type == 'text': brain = Brain(config.get('Brain', 'path') + str(chat_id) + ".brain") brain.learn(msg['text']) if 'reply_to_message' in msg and msg['reply_to_message']['from'][ 'username'] == "Braulio_bot": bot.sendMessage(chat_id, brain.reply(msg['text']), reply_to_message_id=msg['message_id']) elif 'braulio' in msg['text'].lower(): bot.sendMessage( chat_id, brain.reply(msg['text']).replace("Braulio", msg['from']['first_name']))
def _reply(self, irc, channel, text): """Send a response to text""" cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') cobeBrain.learn(response) # Let's have the bot learn the wacky things it says self.log.info("Attempting to respond in {0} with message: {1}".format(channel, response)) # delay the response here so we look real? if self.registryValue('responseDelay', channel): self.log.info("Delayed the response in %s." % channel) delayseconds = time.time() + random.randint(2, 5) schedule.addEvent(irc.queueMsg(ircmsgs.privmsg(channel, response)), delayseconds) else: irc.queueMsg(ircmsgs.privmsg(channel, response))
def learn(archivepath, brain, **kwargs): # start brain. Batch saves us from lots of I/O brain = Brain(brain) brain.set_stemmer(kwargs.get('language', 'english')) brain.start_batch_learning() tweets = tweet_generator(archivepath, **kwargs) count = 0 for text in tweets: count = count + 1 brain.learn(text) brain.stop_batch_learning() return count
def train_brain(self, channel): """ create a cobe brain file based on the db. This file is used by cobe to generate responses. """ logger.debug('starting training') logger.debug('ignored: {}'.format(IGNORED)) # replace the current brain try: os.remove('brain.ai') except: pass BRAIN = Brain('brain.ai') logger.debug('created brain.ai') start = time.time() BRAIN.start_batch_learning() logger_lines = db.logger.find({ 'channel': channel, 'nick': { '$nin': IGNORED }, 'message': { '$regex': '^(?!\.|\,|\!)' }, }) logger.debug('log total: {}'.format(logger_lines.count())) for line in logger_lines: BRAIN.learn(line['message']) BRAIN.stop_batch_learning() logger.debug('learned stuff. Took {:.2f}s'.format(time.time() - start))
def chatbot(botname, remotebot, out=sys.stdout): mem = Brain(botname + ".brain") mem.learn("This is the only thing I know!") msg = "Hello!" out.write("BEGIN LOG: %s\n" % time.ctime()) try: while True: out.write("Local: %s\n" % msg) msg = remotebot.think(msg) out.write("Remote: %s\n" % msg) if LEARN == True: mem.learn(msg) msg = mem.reply(msg) out.flush() except (KeyboardInterrupt, SystemExit, EOFError): print "Saving..." out.write("END LOG: %s\n" % time.ctime()) out.close()
def _reply(self, irc, msg, channel, text): """Send a response to text""" cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) cobeBrain.learn( response) # Let's have the bot learn the wacky things it says self.log.info("Attempting to respond in {0} with message: {1}".format( channel, response)) # delay the response here so we look real? if self.registryValue('responseDelay', channel): self.log.info("Delayed the response in %s." % channel) delayseconds = time.time() + random.randint(2, 5) schedule.addEvent(irc.queueMsg(ircmsgs.privmsg(channel, response)), delayseconds) else: irc.queueMsg(ircmsgs.privmsg(channel, response))
class Markov(ModuleInterface): help = "Markov - Yeah I'm sentient, what of it?" def onEnable(self): self.brain = Brain(os.path.join("hubbot", "data", "{}.brain".format(self.bot.server))) def addToBrain(self, msg): if "://" not in msg and len(msg) > 1: self.brain.learn(msg) def shouldTrigger(self, message): """ @type message: hubbot.message.IRCMessage """ if message.Type in self.acceptedTypes: return True return False def onTrigger(self, message): """ @type message: hubbot.message.IRCMessage """ if message.User.Name == self.bot.nickname: return elif message.TargetType is TargetTypes.USER and not message.MessageString.startswith(self.bot.commandChar): reply = self.brain.reply(message.MessageString, max_len=100) return IRCResponse(ResponseType.Say, reply.capitalize(), message.ReplyTo) elif self.bot.nickname.lower() in message.MessageString.lower() and len(message.MessageList) > 1: messageList = [item.lower() for item in message.MessageList if item.lower() != self.bot.nickname.lower()] reply = self.brain.reply(" ".join(messageList), max_len=100) nickList = [nick.lower() for nick in self.bot.channels[message.ReplyTo].Users.keys()] for word in reply.split(): if word.lower() in nickList: reply.replace(word, message.User.Name) return IRCResponse(ResponseType.Say, reply.capitalize(), message.ReplyTo) else: messageList = [item.lower() for item in message.MessageList if item.lower() != self.bot.nickname.lower()] self.addToBrain(" ".join(messageList))
from os import path from cobe.brain import Brain b = Brain("%s/shakespeare.sqlite" % path.split(path.abspath(__file__))[0]) b.start_batch_learning() with open("shakespeare.txt") as f: for l in f: b.learn(l) b.stop_batch_learning()
def testLearn(self): Brain.init(TEST_BRAIN_FILE, order=2) brain = Brain(TEST_BRAIN_FILE) brain.learn("this is a test") brain.learn("this is also a test")
'screen_name': account, 'count': 200, 'exclude_replies': True, 'include_rts': False } if account in state['accounts']: last_tweet = long(state['accounts'][account]) params['since_id'] = last_tweet else: last_tweet = 0 timeline = api.statuses.user_timeline(**params) for tweet in timeline: b.learn(tweet['text']) #add it to the db db_manager.insert_tweet(tweet['text'].encode('utf-8', 'replace'), False) last_tweet = max(tweet['id'], last_tweet) tweets += 1 print "%d found..." % tweets state['accounts'][account] = str(last_tweet) print "Learning %d tweets" % tweets b.stop_batch_learning() #close the learned txt open(os.path.join(os.path.dirname(__file__), '.state'), 'w').write(dumps(state))
def teach(self, irc, msg, args, channel, text): """[<channel>] <text> Teaches the bot <text>. If the channel is not given, the current channel is used. """ if not channel: # Did the user enter in a channel? If not, set the current channel channel = msg.args[0] if not irc.isChannel(msg.args[0]) and irc.isChannel(channel): # Are we in a channel and is the channel supplied a channel? if os.path.exists(self._getBrainDirectoryForChannel(channel)): # Does this channel have a brain file? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain( self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) else: irc.error(_("No text to learn!"), Raise=True) else: # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format( self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain( self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) elif os.path.exists(self._getBrainDirectoryForChannel( channel)) and irc.isChannel(channel): # We are in a channel! Does the brain file exist and are we supplied a channel? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) else: irc.error(_("No text to learn!"), Raise=True) elif not os.path.exists(self._getBrainDirectoryForChannel( channel)) and irc.isChannel(channel): # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format( self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) else: irc.error(_("Improper channel given!"), Raise=True)
def respond(self, irc, msg, args, channel, text): """[<channel>] <text> Replies to <text>. If the channel is not given, the current channel is used. """ if not channel: # Did the user enter in a channel? If not, set the current channel channel = msg.args[0] if not irc.isChannel(msg.args[0]) and irc.isChannel(channel): # Are we in a channel and is the channel supplied a channel? if os.path.exists(self._getBrainDirectoryForChannel(channel)): # Does this channel have a brain file? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain(self.brainDirectories[channel]) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) for i in range(response.lower().count( self.magicnick.lower())): # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick.lower(), re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) else: irc.error(_("No text to reply to!"), Raise=True) else: # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format( self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain( self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) for i in range(response.lower().count( self.magicnick.lower())): # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick.lower(), re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) # lowercase first letter of the string. response = response[0].lower() + response[1:] irc.reply(response) else: irc.error(_("No text to reply to!"), Raise=True) elif os.path.exists(self._getBrainDirectoryForChannel( channel)) and irc.isChannel(channel): # We are in a channel! Does the brain file exist and is this a channel? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') response = self._strip_nick(irc, msg, response) # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick.lower(), re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) # lowercase first letter of the string. response = response[0].lower() + response[1:] irc.reply(response) else: irc.error(_("No text to reply to!"), Raise=True) elif not os.path.exists(self._getBrainDirectoryForChannel( channel)) and irc.isChannel(channel): # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format( self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = self._strip_nick(irc, msg, response) # If first word is nick, switch with the callers nick. if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick, re.sub( r'[Kk]', r'κ', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick in response: response = response.replace( self.magicnick, re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) if self.magicnick.lower() in response: response = response.replace( self.magicnick.lower(), re.sub( r'[Ee]', r'ε', re.sub(r'^(.)', r'\1', random.choice( list(irc.state.channels[ msg.args[0]].users)), count=1))) cobeBrain.learn(text) else: irc.error(_("No text to reply to!"), Raise=True) else: irc.error(_("Improper channel given!"), Raise=True)
class wormgas(SingleServerIRCBot): channel_ids = { u'rw': 1, u'game': 1, u'oc': 2, u'ocr': 2, u'vw': 3, u'mw': 3, u'cover': 3, u'covers': 3, u'bw': 4, u'chip': 4, u'ch': 4, u'ow': 5, u'omni': 5, u'all': 5 } def __init__(self): self.path, self.file = os.path.split(_abspath) self.brain = Brain(u'{}/brain.sqlite'.format(self.path)) config_json = u'{}/config.json'.format(self.path) rps_json = u'{}/rps.json'.format(self.path) keys_json = u'{}/keys.json'.format(self.path) self.config = dbaccess.Config(config_json, rps_json, keys_json) # Load plugins for plug_name in self.config.get(u'plugins', list())[:]: public, private = self.handle_load([u'!load', plug_name]) for m in private: log.info(m) self.mb = util.CollectionOfNamedLists(u'{}/mb.json'.format(self.path)) self.tf = util.TitleFetcher() args = sys.argv[1:] for arg in args: if arg.startswith(u'--set-'): key, value = arg[6:].split(u'=', 1) print u'Setting \'{}\' to \'{}\'.'.format(key, value) self.config.set(key, value) # Set up ignore if the ignore list is non-empty. ignore = self.config.get(u'msg:ignore', u'') self.reignore = None if ignore: self.reignore = re.compile(ignore, re.IGNORECASE) server = self.config.get(u'irc:server') nick = self.config.get(u'irc:nick') name = self.config.get(u'irc:name') SingleServerIRCBot.__init__(self, [(server, 6667)], nick, name) self.connection.buffer_class.errors = u'replace' def stop(self): '''Save all data and shut down the bot.''' del self.config del self.brain def _dispatcher(self, c, e): et = e.type if et not in self._events_not_logged(): s = e.source t = e.target log.debug(u'{}, {}, {} -- {}'.format(et, s, t, e.arguments)) SingleServerIRCBot._dispatcher(self, c, e) def _aux_wa(self, query): '''Auxilliary function that does the Wolfram API magic.''' apikey = self.config.get(u'wa:apikey', None) if apikey is None: return [u'Wolfram Alpha API key not configured, cannot use !wa.'] try: url = u'http://api.wolframalpha.com/v2/query' payload = { u'appid': apikey, u'input': query, u'format': u'plaintext' } r = requests.get(url, timeout=10, params=payload) root = xml.etree.ElementTree.fromstring(r.text.encode(u'utf-8')) if root.get(u'success') != u'true': return [u'Wolfram Alpha found no answer.'] plaintext = root.find(u'./pod[@primary="true"]/subpod/plaintext') if plaintext is None: for pod in root.findall(u'./pod'): if pod.get(u'title') != u'Input interpretation': plaintext = pod.find(u'./subpod/plaintext') if plaintext is not None: break if plaintext is None: return [u'Error: could not find response.'] if plaintext.text is None: return [u'Error: empty response.'] return plaintext.text.splitlines() except requests.exceptions.Timeout: return [u'Error: Wolfram Alpha timed out.'] except xml.etree.ElementTree.ParseError: return [u'Error: could not parse response.'] except Exception as e: log.exception(e) return [u'Error: An unknown error occurred.'] @command_handler(u'!wa (?P<query>.+)') def handle_wa(self, nick, channel, query=None): '''Ask something of the Wolfram Alpha API.''' log.info(u'{} used !wa'.format(nick)) self.mb.clear(nick) result = self._aux_wa(query) # Private messages always get the full result if channel == PRIVMSG: self.mb.set(nick, result) return # Otherwise, check for the cooldown and respond accordingly. ltw = int(self.config.get(u'lasttime:wa', 0)) ww = int(self.config.get(u'wait:wa', 0)) if ltw < time.time() - ww: # If sending to the channel, send at most 5 lines. self.mb.set(channel, result[:5]) self.config.set(u'lasttime:wa', time.time()) else: self.mb.set(nick, result) wait = ltw + ww - int(time.time()) r = u'I am cooling down. You cannot use !wa in ' r += u'{} for another {} seconds.'.format(channel, wait) self.mb.add(nick, r) @command_handler(u'!8ball') def handle_8ball(self, nick, channel): '''Ask a question of the magic 8ball.''' log.info(u'{} used !8ball'.format(nick)) self.mb.clear(nick) result = random.choice(self._answers_8ball()) # Private messages always get the result. if channel == PRIVMSG: self.mb.add(nick, result) return # Otherwise, check for the cooldown and respond accordingly. ltb = int(self.config.get(u'lasttime:8ball', 0)) wb = int(self.config.get(u'wait:8ball', 0)) if ltb < time.time() - wb: self.mb.add(channel, result) if u'again' not in result: self.config.set(u'lasttime:8ball', time.time()) else: self.mb.add(nick, result) wait = ltb + wb - int(time.time()) r = u'I am cooling down. You cannot use !8ball in ' r += u'{} for another {} seconds.'.format(channel, wait) self.mb.add(nick, r) @command_handler(r'^!$') def handle_bang(self, nick, channel): '''Get messages from the message buffer.''' log.info(u'{} used !'.format(nick)) pass @command_handler(u'^!help(\s(?P<topic>\w+))?') def handle_help(self, nick, channel, topic=None): '''Look up help about a topic''' log.info(u'{} used !help'.format(nick)) self.mb.clear(nick) self._help(nick, topic) def _help(self, nick, topic): is_admin = self._is_admin(nick) rs = list() chan_code_ls = u'\x02, \x02'.join(self.channel_ids.keys()) channelcodes = (u'Channel codes are \x02{}\x02.'.format(chan_code_ls)) notpermitted = u'You are not permitted to use this command.' wiki = (u'More help is available at ' u'https://github.com/williamjacksn/wormgas/wiki') if topic in [u'all', None]: rs.append(u'Use \x02!help [<topic>]\x02 with one of these topics: ' u'8ball, flip, id, key, nowplaying, prevplayed, roll, ' u'rps, rq, wa.') if is_admin: rs.append(u'Administration topics: restart, set, stop, unset.') rs.append(wiki) elif topic == u'8ball': rs.append(u'Use \x02!8ball\x02 to ask a question of the magic ' u'8ball.') elif topic == u'wa': rs.append(u'Use \x02!wa <query>\x02 to query Wolfram Alpha.') elif topic == u'flip': rs.append(u'Use \x02!flip\x02 to flip a coin.') elif topic == u'id': rs.append(u'Look up your Rainwave user id at ' u'http://rainwave.cc/auth/ and use \x02!id add <id>\x02 ' u'to tell me about it.') rs.append(u'Use \x02!id drop\x02 to delete your user id and ' u'\x02!id show\x02 to see it.') elif topic == u'key': rs.append(u'Get an API key from http://rainwave.cc/auth/ and use ' u'\x02!key add <key>\x02 to tell me about it.') rs.append(u'Use \x02!key drop\x02 to delete your key and \x02!key ' u'show\x02 to see it.') elif topic in [u'nowplaying', u'np']: rs.append(u'Use \x02!nowplaying <channel>\x02 to show what is now ' u'playing on the radio.') rs.append(u'Short version is \x02!np<channel>\x02.') rs.append(channelcodes) elif topic in [u'prevplayed', u'pp']: rs.append(u'Use \x02!prevplayed <channel> [<index>]\x02 to show ' u'what was previously playing on the radio.') rs.append(u'Short version is \x02!pp<channel> [<index>]\x02.') rs.append(u'Index should be one of (0, 1, 2), 0 is default, ' u'higher numbers are further in the past.') rs.append(channelcodes) elif topic == u'restart': if is_admin: rs.append(u'Use \x02!restart\x02 to restart the bot.') else: rs.append(notpermitted) elif topic == u'roll': rs.append(u'Use \x02!roll [#d^]\x02 to roll a ^-sided die # ' u'times.') elif topic == u'rps': rs.append(u'Use \x02!rock\x02, \x02!paper\x02, or ' u'\x02!scissors\x02 to play a game.') rs.append(u'Use \x02!rps record [<nick>]\x02 to see the record ' u'for <nick>, leave off <nick> to see your own record, ' u'use nick \'!global\' to see the global record.') rs.append(u'Use \x02!rps stats [<nick>]\x02 to see some ' u'statistics for <nick>, leave off <nick> to see your ' u'own statistics.') rs.append(u'Use \x02!rps reset\x02 to reset your record and ' u'delete your game history, there is no confirmation ' u'and this cannot be undone.') rs.append(u'Use \x02!rps who\x02 to see a list of known players') if is_admin: rs.append(u'Administrators can use \x02!rps rename <oldnick> ' u'<newnick>\x02 to reassign stats and game history ' u'from one nick to another.') elif topic == u'rq': rs.append(u'Use \x02!rq <song_id>\x02 to add a song to your ' u'request queue, find the <song_id> using ' u'\x02!lookup\x02 or \x02!unrated\x02.') rs.append(u'Use \x02!rq unrated\x02 to fill your request queue ' u'with unrated songs.') rs.append(u'Use \x02!rq fav\x02 to add favourite songs to your ' u'request queue.') rs.append(u'Use \x02!rq pause\x02 to pause your request queue).') rs.append(u'Use \x02!rq resume\x02 to resume your request queue).') rs.append(u'Use \x02!rq clear\x02 to remove all songs from your ' u'request queue.') elif topic == u'set': if is_admin: rs.append(u'Use \x02!set [<id>] [<value>]\x02 to display or ' u'change configuration settings.') rs.append(u'Leave off <value> to see the current setting.') rs.append(u'Leave off <id> and <value> to see a list of all ' u'available config ids.') else: rs.append(notpermitted) elif topic == u'stop': if is_admin: rs.append(u'Use \x02!stop\x02 to shut down the bot.') else: rs.append(notpermitted) elif topic == u'unset': if is_admin: rs.append(u'Use \x02!unset <id>\x02 to remove a configuration ' u'setting.') else: rs.append(notpermitted) else: rs.append(u'I cannot help you with \'{}\''.format(topic)) rs.append(wiki) for r in rs: self.mb.add(nick, r) @command_handler(u'^!id(\s(?P<mode>\w+))?(\s(?P<id>\d+))?') def handle_id(self, nick, channel, mode=None, uid=None): '''Manage correlation between an IRC nick and Rainwave User ID Arguments: mode: string, one of 'add', 'drop', 'show' uid: numeric, the person's Rainwave User ID''' log.info(u'{} used !id'.format(nick)) self.mb.clear(nick) if mode == u'add' and user_id: self.config.add_id_to_nick(uid, nick) r = u'I assigned the user id {} to nick \'{}\'.'.format(uid, nick) self.mb.add(nick, r) elif mode == u'drop': self.config.drop_id_for_nick(nick) r = u'I dropped the user id for nick \'{}\'.'.format(nick) self.mb.add(nick, r) elif mode == u'show': stored_id = self.config.get_id_for_nick(nick) if stored_id: r = u'The user id for \'{}\' is {}.'.format(nick, stored_id) self.mb.add(nick, r) else: r = u'I do not have a user id for nick \'{}\'.'.format(nick) self.mb.add(nick, r) else: self._help(nick, topic=u'id') @command_handler(u'^!key(\s(?P<mode>\w+))?(\s(?P<key>\w{10}))?') def handle_key(self, nick, channel, mode=None, key=None): '''Manage API keys Arguments: mode: string, one of 'add', 'drop', 'show' key: string, the API key to add''' log.info(u'{} used !key'.format(nick)) self.mb.clear(nick) if mode == u'add' and key: self.config.add_key_to_nick(key, nick) r = u'I assigned the API key \'{}\' to \'{}\'.'.format(key, nick) self.mb.add(nick, r) elif mode == u'drop': self.config.drop_key_for_nick(nick) r = u'I dropped the API key for \'{}\'.'.format(nick) self.mb.add(nick, r) elif mode == u'show': stored_id = self.config.get_key_for_nick(nick) if stored_id: r = u'The API key for \'{}\' is'.format(nick) r = u'{} \'{}\'.'.format(r, stored_id) self.mb.add(nick, r) else: r = u'I do not have an API key for nick \'{}\'.'.format(nick) self.mb.add(nick, r) else: self._help(nick, topic=u'key') def handle_load(self, tokens): public = list() private = list() if len(tokens) < 2: private.append(u'Please specify a plugin to load.') return public, private plug_name = tokens[1] module_name = u'plugins.{}'.format(plug_name) if module_name in sys.modules: module = reload(sys.modules[module_name]) else: try: module = importlib.import_module(module_name) except ImportError: err = u'Error while loading plugin: {}.'.format(plug_name) log.exception(err) private.append(err) return public, private plugins = set(self.config.get(u'plugins', list())) plugins.add(plug_name) self.config.set(u'plugins', list(plugins)) for plug_handler in inspect.getmembers(module, inspect.isclass): cls = plug_handler[1] cmd_dict = _plug_commands if cls.admin: cmd_dict = _plug_commands_admin for cmd in cls.cmds: cmd_dict[cmd] = cls.handle private.append(u'Loaded a command: {}.'.format(cmd)) return public, private def handle_unload(self, tokens): public = list() private = list() if len(tokens) < 2: private.append(u'Please specify a plugin to load.') return public, private plug_name = tokens[1] module_name = u'plugins.{}'.format(plug_name) plugins = set(self.config.get(u'plugins', list())) if plug_name in plugins: plugins.remove(plug_name) self.config.set(u'plugins', list(plugins)) if module_name in sys.modules: module = sys.modules.get(module_name) for plug_handler in inspect.getmembers(module, inspect.isclass): cls = plug_handler[1] cmd_dict = _plug_commands if cls.admin: cmd_dict = _plug_commands_admin for cmd in cls.cmds: if cmd in cmd_dict: del cmd_dict[cmd] private.append(u'Unloaded a command: {}'.format(cmd)) else: private.append(u'Command not found: {}'.format(cmd)) else: private.append(u'Plugin not loaded: {}'.format(plug_name)) return public, private @command_handler(u'!restart') def handle_restart(self, nick, channel): '''Restart the bot''' log.info(u'{} used !restart'.format(nick)) if self._is_admin(nick): self.config.set(u'restart_on_stop', 1) self.handle_stop(nick, channel) else: log.warning(u'{} does not have privs to use !restart'.format(nick)) @command_handler(u'!roll(\s(?P<dice>\d+)(d(?P<sides>\d+))?)?') def handle_roll(self, nick, channel, dice=None, sides=None): '''Roll some dice''' log.info(u'{} used !roll'.format(nick)) self.mb.clear(nick) try: dice = min(int(dice), 100) except TypeError: dice = 1 try: sides = max(min(int(sides), 100), 1) except TypeError: sides = 20 rolls = [] for i in range(dice): rolls.append(random.randint(1, sides)) r = u'{}d{}: '.format(dice, sides) if dice > 1 and dice < 11: r += u'[' + u', '.join(map(str, rolls)) + u'] = ' r += u'{}'.format(sum(rolls)) if channel == PRIVMSG: self.mb.add(nick, r) return ltr = int(self.config.get(u'lasttime:roll', 0)) wr = int(self.config.get(u'wait:roll', 0)) if ltr < time.time() - wr: self.mb.add(channel, r) self.config.set(u'lasttime:roll', time.time()) else: self.mb.add(nick, r) wait = ltr + wr - int(time.time()) r = u'I am cooling down. You cannot use !roll in ' r += u'{} for another {} seconds.'.format(channel, wait) self.mb.add(nick, r) @command_handler(u'!(?P<mode>rock|paper|scissors)') def handle_rps(self, nick, channel, mode=None): '''Rock, paper, scissors''' if mode is None: return else: mode = mode.lower() log.info(u'{} used !{}'.format(nick, mode)) self.mb.clear(nick) rps = [u'rock', u'paper', u'scissors'] challenge = rps.index(mode) response = random.randint(0, 2) self.config.log_rps(nick, challenge, response) r = u'You challenge with {}. I counter with'.format(mode) r = u'{} {}. '.format(r, rps[response]) if challenge == (response + 1) % 3: r += u'You win!' elif challenge == response: r += u'We draw!' elif challenge == (response + 2) % 3: r += u'You lose!' w, d, l = self.config.get_rps_record(nick) pw = int(float(w) / float(w + d + l) * 100) pd = int(float(d) / float(w + d + l) * 100) pl = int(float(l) / float(w + d + l) * 100) r += u' Your current record is ' r += u'{}-{}-{} or {}%-{}%-{}% (w-d-l).'.format(w, d, l, pw, pd, pl) if channel == PRIVMSG: self.mb.add(nick, r) return ltr = int(self.config.get(u'lasttime:rps', 0)) wr = int(self.config.get(u'wait:rps', 0)) if ltr < time.time() - wr: self.mb.add(channel, r) self.config.set(u'lasttime:rps', time.time()) else: self.mb.add(nick, r) wait = ltr + wr - int(time.time()) r = u'I am cooling down. You cannot use !{}'.format(mode) r = u'{} in {} for another {} seconds.'.format(r, channel, wait) self.mb.add(nick, r) @command_handler(u'^!rps record(\s(?P<target>\S+))?') def handle_rps_record(self, nick, channel, target=None): '''Report RPS record for a nick''' log.info(u'{} used !rps record'.format(nick)) self.mb.clear(nick) if target is None: target = nick w, d, l = self.config.get_rps_record(target) total = sum((w, d, l)) r = u'RPS record for {} ({} game'.format(target, total) if total != 1: r += u's' r += u') is {}-{}-{} (w-d-l).'.format(w, d, l) if channel == PRIVMSG: self.mb.add(nick, r) return ltr = int(self.config.get(u'lasttime:rps', 0)) wr = int(self.config.get(u'wait:rps', 0)) if ltr < time.time() - wr: self.mb.add(channel, r) self.config.set(u'lasttime:rps', time.time()) else: self.mb.add(nick, r) wait = ltr + wr - int(time.time()) m = u'I am cooling down. You cannot use !rps in ' m += u'{} for another {} seconds.'.format(channel, wait) self.mb.add(nick, m) @command_handler(u'!rps rename(\s(?P<old>\S+))?(\s(?P<new>\S+))?') def handle_rps_rename(self, nick, channel, old=None, new=None): '''Rename an RPS nick, useful for merging game histories''' log.info(u'{} used !rps rename'.format(nick)) if self._is_admin(nick) and old and new: self.mb.clear(nick) self.config.rename_rps_player(old, new) r = u'I assigned RPS game history for {} to {}.'.format(old, new) self.mb.add(nick, r) @command_handler(u'^!rps reset') def handle_rps_reset(self, nick, channel): '''Reset RPS stats and delete game history for a nick''' log.info(u'{} used !rps reset'.format(nick)) self.mb.clear(nick) self.config.reset_rps_record(nick) r = u'I reset your RPS record and deleted your game history.' self.mb.add(nick, r) @command_handler(u'!rps stats(\s(?P<target>\S+))?') def handle_rps_stats(self, nick, channel, target=None): '''Get some RPS statistics for a player''' log.info(u'{} used !rps stats'.format(nick)) self.mb.clear(nick) if target is None: target = nick totals = self.config.get_rps_challenge_totals(target) games = sum(totals) if games > 0: r_rate = totals[0] / float(games) * 100 p_rate = totals[1] / float(games) * 100 s_rate = totals[2] / float(games) * 100 r = target r += u' challenges with rock/paper/scissors at these rates: ' r += u'{:3.1f}/{:3.1f}/{:3.1f}%.'.format(r_rate, p_rate, s_rate) else: r = u'{} does not play. :('.format(target) if channel == PRIVMSG: self.mb.add(nick, r) return ltr = int(self.config.get(u'lasttime:rps', 0)) wr = int(self.config.get(u'wait:rps', 0)) if ltr < time.time() - wr: self.mb.add(channel, r) self.config.set(u'lasttime:rps', time.time()) else: self.mb.add(nick, r) wait = ltr + wr - int(time.time()) r = u'I am cooling down. You cannot use !rps in {}'.format(channel) r = u'{} for another {} seconds.'.format(r, wait) self.mb.add(nick, r) @command_handler(u'^!rps who') def handle_rps_who(self, nick, channel): '''List all players in the RPS game history''' log.info(u'{} used !rps who'.format(nick)) self.mb.clear(nick) players = self.config.get_rps_players() mlnl = int(self.config.get(u'maxlength:nicklist', 10)) while len(players) > mlnl: plist = players[:mlnl] players[:mlnl] = [] r = u'RPS players: ' + u', '.join(plist) self.mb.add(nick, r) r = u'RPS players: ' + u', '.join(players) self.mb.add(nick, r) @command_handler(u'^!set(\s(?P<id>\S+))?(\s(?P<value>.+))?') def handle_set(self, nick, channel, id=None, value=None): '''View and set bot configuration''' log.info(u'{} used !set'.format(nick)) self.mb.clear(nick) if self._is_admin(nick): self.mb.set(nick, self.config.handle(id, value)) else: log.warning(u'{} does not have privs to use !set'.format(nick)) @command_handler(u'!stop') def handle_stop(self, nick, channel): '''Shut down the bot''' log.info(u'{} used !stop'.format(nick)) if self._is_admin(nick): if self.config.get(u'restart_on_stop'): self.config.unset(u'restart_on_stop') pid = subprocess.Popen( [_abspath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) self.die(u'I was stopped by {}'.format(nick)) else: log.warning(u'{} does not have privs to use !stop'.format(nick)) @command_handler(u'^!unset(\s(?P<id>\S+))?') def handle_unset(self, nick, channel, id=None): '''Unset a configuration item''' log.info(u'{} used !unset'.format(nick)) if self._is_admin(nick): if id: self.config.unset(id) self.mb.clear(nick) self.mb.add(nick, u'{} has been unset.'.format(id)) return else: log.warning(u'{} does not have privs to use !unset'.format(nick)) def on_join(self, c, e): '''This method is called when an IRC join event happens Arguments: c: the Connection object asociated with this event e: the Event object''' nick = e.source.nick irc_chan = e.target # Check for a join response jr = self.config.get(u'joinresponse:{}'.format(nick)) if jr: self._to_irc(c, u'privmsg', irc_chan, jr) ja = self.config.get(u'joinaction:{}'.format(nick)) if ja: self._to_irc(c, u'action', irc_chan, ja) def on_ping(self, c, e): '''This method is called when an IRC ping event happens''' # Start the periodic tasks. self._periodic(c) def on_privmsg(self, c, e): '''This method is called when a message is sent directly to the bot Arguments: c: the Connection object associated with this event e: the Event object''' nick = e.source.nick me = e.target msg = e.arguments[0].strip() chan = self.config.get(u'irc:channel') command_handled = False # Core commands tokens = msg.split() if len(tokens) == 0: return cmd = tokens[0].lower() if cmd == u'!load': command_handled = True if self._is_admin(nick): public, private = self.handle_load(tokens) self.mb.set(chan, public) self.mb.set(nick, private) else: self.mb.add(nick, u'You are not an admin.') if cmd == u'!unload': command_handled = True if self._is_admin(nick): public, private = self.handle_unload(tokens) self.mb.set(chan, public) self.mb.set(nick, private) else: self.mb.add(nick, u'You are not an admin.') # Try admin commands from plugins if not command_handled: if self._is_admin(nick) and cmd in _plug_commands_admin: command_handled = True handler = _plug_commands_admin.get(cmd) try: public, private = handler(nick, me, tokens, self.config) self.mb.set(chan, public) self.mb.set(nick, private) except: log.exception(u'Exception in {}'.format(cmd)) m = (u'I experienced a problem and recorded some ' u'exception information in the log.') self.mb.set(nick, [m]) # Try normal commands from plugins if not command_handled: if cmd in _plug_commands: command_handled = True handler = _plug_commands.get(cmd) try: public, private = handler(nick, me, tokens, self.config) self.mb.set(chan, public) self.mb.set(nick, private) except: log.exception(u'Exception in {}'.format(cmd)) m = (u'I experienced a problem and recorded some ' u'exception information in the log.') self.mb.set(nick, [m]) # Try all the decorated command handlers for command in _commands: if command(self, nick, msg, PRIVMSG): command_handled = True break if not command_handled: # No responses from the commands, punt to the brain self.mb.clear(nick) self.mb.add(nick, self._talk(msg)) # Send responses while self.mb.items(chan): self._to_irc(c, u'privmsg', chan, self.mb.pop(chan, 0)) rs = list() while len(rs) < 7 and self.mb.items(nick): rs.append(self.mb.pop(nick, 0)) if len(self.mb.items(nick)) == 1: rs.append(self.mb.pop(nick, 0)) if len(self.mb.items(nick)) > 1: num = len(self.mb.items(nick)) r = u'Use \x02!\x02 to see more messages ({} left).'.format(num) rs.append(r) for r in rs: self._to_irc(c, u'privmsg', nick, r) def on_pubmsg(self, c, e): '''This method is called when a message is sent to the channel the bot is on Arguments: c: the Connection object associated with this event e: the Event object''' nick = e.source.nick msg = e.arguments[0].strip() chan = e.target command_handled = False # Core commands tokens = msg.split() if len(tokens) == 0: return cmd = tokens[0].lower() if cmd == u'!load': command_handled = True if self._is_admin(nick): public, private = self.handle_load(tokens) self.mb.set(chan, public) self.mb.set(nick, private) else: self.mb.add(nick, u'You are not an admin.') if cmd == u'!unload': command_handled = True if self._is_admin(nick): public, private = self.handle_unload(tokens) self.mb.set(chan, public) self.mb.set(nick, private) else: self.mb.add(nick, u'You are not an admin.') # Try admin commands from plugins if not command_handled: if self._is_admin(nick) and cmd in _plug_commands_admin: command_handled = True handler = _plug_commands_admin.get(cmd) try: public, private = handler(nick, chan, tokens, self.config) self.mb.set(chan, public) self.mb.set(nick, private) except: log.exception(u'Exception in {}'.format(cmd)) m = (u'I experienced a problem and recorded some ' u'exception information in the log.') self.mb.set(nick, [m]) # Try normal commands from plugins if not command_handled: if cmd in _plug_commands: command_handled = True handler = _plug_commands.get(cmd) try: public, private = handler(nick, chan, tokens, self.config) self.mb.set(chan, public) self.mb.set(nick, private) except: log.exception(u'Exception in {}'.format(cmd)) m = (u'I experienced a problem and recorded some ' u'exception information in the log.') self.mb.set(nick, [m]) # Try all the decorated command handlers if not command_handled: for command in _commands: if command(self, nick, msg, chan): command_handled = True break # If there are no responses from the commands, look for URLs title_found = False if not command_handled: urls = self._find_urls(msg) for url in urls: title = None try: title = self.tf.get_title(url) except util.TitleFetcherError as err: log.exception(err) if title: log.info(u'Found a title: {}'.format(title)) title_found = True self.mb.add(chan, u'[ {} ]'.format(title)) # If there are no URLs, punt to the brain if not (command_handled or title_found): talkr = self._talk(msg) if talkr: self.config.set(u'msg:last', msg) self.config.set(u'lasttime:msg', time.time()) ltr = int(self.config.get(u'lasttime:respond', 0)) wr = int(self.config.get(u'wait:respond', 0)) if self.config.get(u'irc:nick') in msg: if time.time() > ltr + wr: self.mb.add(chan, talkr) self.config.set(u'msg:last', talkr) self.config.set(u'lasttime:respond', time.time()) else: self._to_irc(c, u'privmsg', nick, talkr) wait = ltr + wr - int(time.time()) r = u'I am cooling down. I cannot respond in ' r += u'{} for another {} seconds.'.format(chan, wait) self._to_irc(c, u'privmsg', nick, r) # Send responses while self.mb.items(chan): r = u'{}: {}'.format(nick, self.mb.pop(chan, 0)) self._to_irc(c, u'privmsg', chan, r) if command_handled: rs = list() while len(rs) < 7 and self.mb.items(nick): rs.append(self.mb.pop(nick, 0)) if len(self.mb.items(nick)) == 1: rs.append(self.mb.pop(nick, 0)) if len(self.mb.items(nick)) > 1: n = len(self.mb.items(nick)) r = u'Use \x02!\x02 to see more messages ({} left).'.format(n) rs.append(r) for r in rs: self._to_irc(c, u'privmsg', nick, r) def on_welcome(self, c, e): '''This method is called when the bot first connects to the server Arguments: c: the Connection object associated with this event e: the Event object''' passwd = self.config.get(u'irc:nickservpass') if passwd is not None: m = u'identify {}'.format(passwd) self._to_irc(c, u'privmsg', u'nickserv', m) c.join(self.config.get(u'irc:channel')) def _answers_8ball(self): return [ u'As I see it, yes.', u'Ask again later.', u'Better not tell you now.', u'Cannot predict now.', u'Concentrate and ask again.', u'Don\'t count on it.', u'It is certain.', u'It is decidedly so.', u'Most likely.', u'My reply is no.', u'My sources say no.', u'Outlook good.', u'Outlook not so good.', u'Reply hazy, try again.', u'Signs point to yes.', u'Very doubtful.', u'Without a doubt.', u'Yes.', u'Yes - definitely.', u'You may rely on it.' ] def _events_not_logged(self): return [ u'all_raw_messages', u'created', u'endofmotd', u'featurelist', u'luserchannels', u'luserclient', u'luserme', u'luserop', u'luserunknown', u'motd', u'motdstart', u'myinfo', u'n_global', u'n_local' ] def _find_urls(self, text): '''Look for URLs in arbitrary text. Return a list of the URLs found.''' log.info(u'Looking for URLs in: {}'.format(text)) urls = [] for token in text.split(): try: o = urlparse(token) except ValueError: log.exception(u'Trouble looking for URLs.') return urls if u'http' in o.scheme and o.netloc: url = o.geturl() log.info(u'Found a URL: {}'.format(url)) urls.append(url) return urls def _is_admin(self, nick): '''Check whether a nick has privileges to use admin commands''' channel = self.config.get(u'irc:channel').encode(u'ascii') if channel in self.channels: chan = self.channels[channel] if nick in chan.owners(): return True elif nick in chan.opers(): return True elif nick in chan.halfops(): return True return False def _periodic(self, c): # If I have not checked for forum activity for 'timeout:forumcheck' # seconds, check now log.info(u'Performing periodic tasks') chan = self.config.get(u'irc:channel') ltm = int(self.config.get(u'lasttime:msg', 0)) toc = int(self.config.get(u'timeout:chat', 3600)) if int(time.time()) > ltm + toc: log.info(u'Chat timeout exceeded, keep the conversation moving') talkr = self._talk() if talkr: self.config.set(u'msg:last', talkr) self.config.set(u'lasttime:msg', time.time()) self.config.set(u'lasttime:respond', time.time()) self.mb.add(chan, talkr) while self.mb.items(chan): self._to_irc(c, u'privmsg', chan, self.mb.pop(chan, 0)) quotes = [ (u'Attack the evil that is within yourself, rather than attacking the ' u'evil that is in others.'), u'Before you embark on a journey of revenge, dig two graves.', u'Better a diamond with a flaw than a pebble without.', u'Everything has beauty, but not everyone sees it.', u'He who knows all the answers has not been asked all the questions.', (u'He who learns but does not think, is lost! He who thinks but does ' u'not learn is in great danger.'), u'I hear and I forget. I see and I remember. I do and I understand.', (u'If what one has to say is not better than silence, then one should ' u'keep silent.'), (u'If you make a mistake and do not correct it, this is called a ' u'mistake.'), (u'Ignorance is the night of the mind but a night without moon and ' u'star.'), (u'Music produces a kind of pleasure which human nature cannot do ' u'without.'), u'Only the wisest and stupidest of men never change.', (u'Our greatest glory is not in never falling, but in rising every ' u'time we fall.'), u'Respect yourself and others will respect you.', u'Silence is a true friend who never betrays.', (u'The hardest thing of all is to find a black cat in a dark room, ' u'especially if there is no cat.'), (u'The man who asks a question is a fool for a minute, the man who ' u'does not ask is a fool for life.'), (u'The superior man is modest in his speech, but exceeds in his ' u'actions.'), u'To be wronged is nothing, unless you continue to remember it.', (u'To see what is right and not to do it, is want of courage or of ' u'principle.'), (u'What you know, you know, what you don\'t know, you don\'t know. ' u'This is true wisdom.') ] def _talk(self, msg=None): '''Engage the brain, respond when appropriate Arguments: msg: the message to learn and possible reply to Returns: a string''' # If I am not replying to anything in particular, use the last message if msg is None: msg = self.config.get(u'msg:last') if msg is None: return random.choice(self.quotes) # Ignore messages with certain words if self.reignore: result = self.reignore.search(msg) if result is not None: return random.choice(self.quotes) # Clean up the message before sending to the brain tobrain = msg tobrain = tobrain.replace(self.config.get(u'irc:nick'), u'') tobrain = tobrain.replace(u':', u'') self.brain.learn(tobrain) return self.brain.reply(tobrain) def _to_irc(self, c, msgtype, target, msg): '''Send an IRC message''' log.debug(u'Sending {} to {} -- {}'.format(msgtype, target, msg)) if hasattr(c, msgtype): f = getattr(c, msgtype) if type(msg) is not unicode: msg = unicode(msg, u'utf-8') try: f(target, msg) except: log.exception(u'Problem sending to IRC') else: log.error(u'Invalid message type \'{}\''.format(msgtype))
class MarkovChainChatter: chattiness = 300 chain_length = 2 commandQueueRef = None chatGeneratorThread = None initialWaitTime = 10 markov = defaultdict(list) STOP_WORD = "\n" write_to_file = False fileLocation = '' max_words = 10000 filteredCharacters = [ '\\', '!', '.', '?', '/', ')', '(', '*', '-', '~', '\n', '\r', '\t', '>' ] emotes = [ ':)', ':(', ':o', ':z', 'B)', ':\\', ';)', ';p', ':p', 'R)', 'o_O', ':D', '>(', '<3', '<3', 'R)', ':>', '<]', ':7', ':(', ':P', ';P', ':O', ':\\', ':|', ':s', ':D', '>(', '#/', 'KappaHD', 'MiniK', 'MiniK', 'imGlitch', 'copyThis', 'pastaThat', '<3', '͡°', 'ʖ' ] faceEmotes = [ 'tppCrit', 'tppMiss', 'tppHax', 'tppRng', 'tppHelix', 'tppPokeyen', 'tppRobored', 'tppRiot', 'tppPc', 'tppDome' ] wordsToFilter = ['raid', 'f**k', 'shit', 'bitch'] brain = None brainCache = [] sendChatMessageLock = threading.Lock() def __init__(self, commandQueueRef, trainingTextFileLocation): self.commandQueueRef = commandQueueRef self.fileLocation = trainingTextFileLocation self.chatGeneratorThread = threading.Thread(target=self.workerThread) self.chatGeneratorThread.start() emotesFileHandle = open('faceEmotes.txt', 'r') for line in emotesFileHandle.readlines(): self.faceEmotes.append(line.replace(' ', '').replace('\n', '')) emotesFileHandle.close() ''' trainingFileHandle = open(self.fileLocation) for line in trainingFileHandle.readlines(): line = line.replace('\n', '').replace('\r', '') if len(line.split(' ')) > (self.chain_length + 1) and line.find('@') < 0: self.brain.learn(line) trainingFileHandle.close() ''' def add_to_brain(self, msg): #self.sendChatMessageLock.acquire() currentMsg = self.cleanString(msg) if len(currentMsg.split(' ')) > 2 and currentMsg.find( '@') < 0: #add new message if it meets certain criteria self.brainCache.append(currentMsg) #self.sendChatMessageLock.release() ''' if self.write_to_file and len(msg.split(' ')) > (self.chain_length + 1): f = open(self.fileLocation, 'a') f.write(msg + '\n') f.close() buf = [self.STOP_WORD] * self.chain_length if len(msg.split(' ')) > (self.chain_length + 1): #must be enough words to store anything for word in msg.split(): self.markov[tuple(buf)].append(word) del buf[0] buf.append(word) self.markov[tuple(buf)].append(self.STOP_WORD) ''' def generate_sentence(self, msg): if self.brain is not None: return self.brain.reply(text=msg, max_len=30) ''' buf = msg.split()[:self.chain_length] if len(msg.split()) > self.chain_length: message = buf[:] else: message = [] for i in xrange(self.chain_length): message.append(random.choice(self.markov[random.choice(self.markov.keys())])) for i in xrange(self.max_words): try: next_word = random.choice(self.markov[tuple(buf)]) except IndexError: continue if next_word == self.STOP_WORD: break message.append(next_word) del buf[0] buf.append(next_word) return ' '.join(message) ''' def cleanString(self, msg): result = msg.lower() for character in self.filteredCharacters: result = result.replace(character, '') for emote in self.emotes: result = result.replace(emote.lower(), '') for faceEmote in self.faceEmotes: result = result.replace(faceEmote.lower(), '') for word in self.wordsToFilter: result = result.replace(word.lower(), '') return result def join(self): self.chatGeneratorThread.join() def workerThread(self): self.brain = Brain(self.fileLocation) #wait a few minutes to load enough information time.sleep(self.initialWaitTime) while True: time.sleep(self.chattiness) if len(self.brainCache) == 0: ''' try: self.sendChatMessageLock.release() except Exception, e: z = e print 'release exception: ' + str(z) ''' print 'there was nothing to reply to' pass else: print 'adding new info from cache: ' + str(self.brainCache) for msg in self.brainCache: #add all current messages to the brain self.brain.learn(msg) usedMessage = max(self.brainCache, key=lambda msg: len(msg)) print 'replying to message: ' + usedMessage message = (usedMessage + '.')[:-1] #get a copy of the longest message del self.brainCache[:] #clear cache after storing the messages in the brain self.brainCache = list() gc.collect() #release lock after making copy ''' try: self.sendChatMessageLock.release() except Exception, e: z = e print 'release exception: ' + str(z) ''' sentence = '' try: sentence = self.generate_sentence( message) #make new message print 'generated sentence: ' + sentence except IndexError, e: a = 1 if len(sentence.split(' ')) > 2 and sentence.find( '@' ) < 0: #send new message if it meets certain criteria self.cleanString(sentence) #print 'chatter says: ' + sentence command = 'PRIVMSG #otherbrand ' + " :" + sentence + '\r\n' self.commandQueueRef.sendCommandToQueue( command, self.initialWaitTime)
#!/usr/bin/env python import sys from cobe.brain import Brain if len(sys.argv) != 2: print "Usage: %s <brain>" % sys.argv[0] sys.exit(1) brain = Brain(sys.argv[1]) while True: data = sys.stdin.readline().decode('utf-8') if len(data) == 0: break cmd, line = data.split(' ', 1) if cmd == 'learn': brain.learn(line) elif cmd == 'reply': reply = brain.reply(line) sys.stdout.write((reply + "\n").encode('utf-8')) sys.stdout.flush()
else: return content[:length].rsplit(' ', 1)[0] for account in config['dump_accounts']: print "Grabbing tweets for %s" % account params = { 'screen_name': account, 'count': 200, 'exclude_replies': True, 'include_rts': False } if account in state['accounts']: last_tweet = long(state['accounts'][account]) params['since_id'] = last_tweet else: last_tweet = 0 timeline = api.statuses.user_timeline(**params) for tweet in timeline: b.learn(tweet['text']) #add it to the db db_manager.insert_tweet(tweet['text'].encode('utf-8', 'replace'), False) last_tweet = max(tweet['id'], last_tweet) tweets += 1 print "%d found..." % tweets state['accounts'][account] = str(last_tweet) print "Learning %d tweets" % tweets b.stop_batch_learning() #close the learned txt open(os.path.join(os.path.dirname(__file__), '.state'), 'w').write(dumps(state))
class cobe(kibot.BaseModule.BaseModule): """intelligence""" _nolearn_uperm = UserPerm("nolearn") def __init__(self, bot): self._brain_file = os.path.join(bot.op.files.data_dir, "cobe.brain") self._trace_file = os.path.join(bot.op.files.data_dir, "cobe.trace") # rotate logs if os.path.exists(self._trace_file): now = datetime.datetime.now() stamp = now.strftime("%Y-%m-%d.%H%M%S") os.rename(self._trace_file, "%s.%s" % (self._trace_file, stamp)) self._brain = Brain(self._brain_file, instatrace=self._trace_file) kibot.BaseModule.BaseModule.__init__(self, bot) def _on_pubmsg(self, conn, event): import string, re message = event.args[0] message = re.sub("<\S+>", "", message) match = re.match('"(.*)" --\S+, \d+-\S+\d+.', message) if match: message = match.group(1) match = re.match("\s*(\S+)\s*[,:]\s*(.*?)\s*$", message) if match: to = match.group(1) text = match.group(2) else: to = None text = message speaker = nm_to_n(event.source) # drop any extra whitespace in the string text = string.join(string.split(text), " ") user = self.bot.ircdb.get_user(nickmask=event.source) if user is not None: uperms = list(user.get_perms()) else: uperms = list(self.bot.permdb.get_unknown_perms()) uperms.append(self._nolearn_uperm) if "ignore" in uperms: return "NO MORE" # convert text to Unicode text = text.decode("utf-8") # if spoken to directly, generate a reply if str(to).lower() == self.bot.nick.lower(): self._brain.learn(text) reply = self._brain.reply(text) # convert to utf-8 for output reply = reply.encode("utf-8") conn.privmsg(event.target, "%s: %s" % (speaker, reply)) else: self._brain.learn(text) return "NO MORE"
from tqdm import tqdm import praw import sys old_stdout = sys.stdout sys.stdout = tqdm brain = Brain("cobe.brain") # Removed to prevent impersonation USER_AGENT = '' USERNAME = '' PASSWORD = '' PINGNAME = '+/u/' + USERNAME interface = praw.Reddit(USER_AGENT) interface.login(username=USERNAME, password=PASSWORD, disable_warning=True) def getreply(comment): body = comment.body return brain.reply(body.replace('+/u/RedditSimulatorBot', '')) if __name__ == '__main__': for comment in tqdm(praw.helpers.comment_stream(interface, 'all'), unit=' comments'): brain.learn(comment.body) if PINGNAME in comment.body: tqdm.write("%") comment.reply(getreply(comment))
last_tweet = long(state['accounts'][account]) else: last_tweet = 0 try: timeline = api.GetUserTimeline( account, count=200, since_id=last_tweet, include_rts=not config.skip_retweets, exclude_replies=config.skip_replies, trim_user=True, include_entities=False ) except: continue for tweet in timeline: b.learn(tweet.text) #add it to the db db_manager.insert_tweet(tweet.text.encode('utf-8', 'replace'), False) last_tweet = max(tweet.id, last_tweet) tweets += 1 print "%d found..." % tweets state['accounts'][account] = str(last_tweet) print "Learning %d tweets" % tweets b.stop_batch_learning() #close the learned txt open(os.path.join(os.path.dirname(__file__), '.state'), 'w').write(dumps(state))
import sys sys.path.append("./cobe/") from cobe.brain import Brain print "Learning from all" text = [] for line in sys.stdin: text.append(line) print "Starting" b = Brain("cobe.brain") map(lambda x: b.learn(x), text) print "Done"
class Cobe(): def __init__(self, config=CONFIG): self.ready = False self.psapi = pushshift_api self.rapi = reddit_api self.config = CONFIG self.brain = Brain(self.config.get("cobe_main_db")) self.size = 0 def get_reply(self, replyto: str=''): if self.ready: return self.brain.reply(replyto) else: log.info(f"cobe not initialized, run init") def init(self): log.info("using cobe to generate comments") main_db = self.config.get("cobe_main_db") # make sure db was initialized correctly if os.path.isfile(main_db): # set the initial size self.size = os.path.getsize(main_db) else: log.info(f"cobe db failed to initialize. exiting") sys.exit() log.debug('filling cobe database for commenting') # loop through learning comments until we reach the min db size while self.size <= tobytes(self.config.get("cobe_min_db_size")): log.info(f"cobe db size is: {str(bytesto(self.size, 'm'))}mb, need {self.config.get('cobe_min_db_size')} - learning...") # just learn from random subreddits for now subreddit = get_subreddit(getsubclass=True) log.info(f"learning from /r/{subreddit}") # get the comment generator function from pushshift comments = self.psapi.get_comments(subreddit) # go through 500 comments per subreddit for x in range(500): # get the comment from the generator function try: comment = next(comments) except StopIteration as e: log.info(f"end of comments") # bot responses are better when it learns from short comments if len(comment.body) < 240: log.debug(f"learning comment: {comment.body.encode('utf8')}") # only learn comments that don't contain an avoid word if not any(word in comment.body for word in AVOID_WORDS): self.brain.learn(comment.body.encode("utf8")) # update the class size variable so the while loop # knows when to break self.size = os.path.getsize(main_db) log.info(f"database min size ({self.config.get('cobe_min_db_size')}) reached") self.ready = True
def respond(self, irc, msg, args, channel, text): """[<channel>] <text> Replies to <text>. If the channel is not given, the current channel is used. """ if not channel: # Did the user enter in a channel? If not, set the current channel channel = msg.args[0] if not irc.isChannel(msg.args[0]) and irc.isChannel(channel): # Are we in a channel and is the channel supplied a channel? if os.path.exists(self._getBrainDirectoryForChannel(channel)): # Does this channel have a brain file? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain(self.brainDirectories[channel]) response = cobeBrain.reply(text).encode('utf-8') irc.reply(response) else: irc.error(_("No text to reply to!"), Raise=True) else: # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format(self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') irc.reply(response) else: irc.error(_("No text to reply to!"), Raise=True) elif os.path.exists(self._getBrainDirectoryForChannel(channel)) and irc.isChannel(channel): # We are in a channel! Does the brain file exist and is this a channel? text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) response = cobeBrain.reply(text).encode('utf-8') irc.reply(response) else: irc.error(_("No text to reply to!"), Raise=True) elif not os.path.exists(self._getBrainDirectoryForChannel(channel)) and irc.isChannel(channel): # Nope, create one! self.log.info("Non-existent brainfile in {0}!".format(channel)) self.log.info("Creating a brainfile now in {0}".format(self._getBrainDirectoryForChannel(channel))) commands.getoutput('{0} {1}'.format(self._doCommand(channel), 'init')) text = self._cleanText(text) if text and len(text) > 1 and not text.isspace(): irc.reply("Learning text: {0}".format(text)) cobeBrain = Brain(self._getBrainDirectoryForChannel(channel)) cobeBrain.learn(text) else: irc.error(_("No text to reply to!"), Raise=True) else: irc.error(_("Improper channel given!"), Raise=True)
if account in state['accounts']: last_tweet = long(state['accounts'][account]) else: last_tweet = 0 try: timeline = api.GetUserTimeline( account, count=200, since_id=last_tweet, include_rts=not config.skip_retweets, exclude_replies=config.skip_replies, trim_user=True, include_entities=False ) except: continue for tweet in timeline: b.learn(tweet.text) last_tweet = max(tweet.id, last_tweet) tweets += 1 print "%d found..." % tweets state['accounts'][account] = str(last_tweet) print "Learning %d tweets" % tweets b.stop_batch_learning() open(os.path.join(os.path.dirname(__file__), '.state'), 'w').write(dumps(state))