From 6717db316f80ea29a4eaced5fb43d82410707d78 Mon Sep 17 00:00:00 2001 From: "james.bertino" Date: Thu, 1 May 2014 17:19:45 -0400 Subject: [PATCH 1/3] Fix for Issue #14 and PEP8 changes. --- bot.py | 244 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 86 deletions(-) diff --git a/bot.py b/bot.py index f80ca22..b648dc4 100644 --- a/bot.py +++ b/bot.py @@ -1,146 +1,218 @@ +######################################################################### +################# Abundant notes are appreciated here! ################## +######################################################################### + # Import some necessary libraries. import socket import time import csv -import string import Queue import random from threading import Thread from re import search -# Some basic variables used to configure the bot +# Some basic variables used to configure the bot. server = "irc.freenode.net" -channel = "#openhatch" -botnick = "WelcomeBot" -waitTime = 60 # Amount of time after joining before bot replies to someone +channel = "#openhatch" +botnick = 'WelcomeBot' +channel_admins = ('shauna', 'paulproteus', 'marktraceur') +wait_time = 60 # amount of time after joining before bot replies to someone +change_wait = botnick + " --wait-time " -# Classes -class newcomer(object): # Newcomer class created when someone joins the room + +#################### Classes #################### +# Newcomer class that is created when someone joins the room. +class NewComer(object): def __init__(self, nick): self.nick = nick self.born = time.time() self.status = 0 - def updateStatus(self): + def update_status(self): self.status = 1 - def aroundFor(self): + def around_for(self): return int(time.time() - self.born) -# Functions! -def joinchan(chan): # Joins channels - ircsock.send("JOIN " + chan + "\n") -def getIRC(): # Creates separate thread for reading messages from the server +#################### Functions! #################### +# Joins specified channel. +def join_channel(chan): + ircsock.send("JOIN {} \n".format(chan)) + + +# Reads the messages from the server and adds them to the Queue and prints +# them to the console. This function will be run in a thread, see below. +def msg_handler(): while True: - ircmsg = ircsock.recv(2048) # receive data from the server - ircmsg = ircmsg.strip('\n\r') # removing any unnecessary linebreaks. - q.put(ircmsg) # Put in queue for main loop to read - print(ircmsg) + new_msg = ircsock.recv(2048) # receive data from the server + new_msg = new_msg.strip('\n\r') # removing any unnecessary linebreaks + q.put(new_msg) # put in queue for main loop to read + print(new_msg) -def ping(): # Responds to server Pings. + +# Responds to server Pings. +def pong(): ircsock.send("PONG :pingis\n") -def hello(actor, greeting, chan=channel): # This function responds to a user that inputs "Hello Mybot" - ircsock.send("PRIVMSG " + chan + " :" + greeting + " " + actor + "\n") -def help(actor, chan=channel): # This function explains what the bot is when queried. - ircsock.send("PRIVMSG " + chan + " :I'm a bot! I'm from here: . You can change my behavior by submitting a pull request or by talking to shauna. \n") +# This function responds to a user that inputs "Hello Mybot". +def bot_hello(greeting): + ircsock.send("PRIVMSG {0} :{1} {2}\n".format(channel, greeting, actor)) -def welcome(newcomer): # This welcomes a specific person. - ircsock.send("PRIVMSG " + channel + " :Welcome " + newcomer + "! The channel's pretty quiet right now, so I thought I'd say hello, and ping some people (like shauna, paulproteus, marktraceur) that you're here. If no one responds for a while, try emailing us at hello@openhatch.org or just coming back later. FYI, you're now on my list of known nicknames, so I won't bother you again.\n") -def makeNickArray(): # On startup, makes array of nicks from Nicks.txt. New info will be written to both array and txt file. - nickArray = [] - with open('nicks.csv', 'rb') as csvfile: - nicksData = csv.reader(csvfile, delimiter=',', quotechar='|') - for row in nicksData: - nickArray.append(row) - return nickArray +# This function explains what the bot is when queried. +def bot_help(): + ircsock.send("PRIVMSG {} :I'm a bot! I'm from here . You can change my behavior by " + "submitting a pull request or by talking to 'fill in " + "the blank'.\n".format(channel)) -def addPerson(person): # Adds newcomer to list of known nicks - person = person.replace("_","") - nickArray.append([person]) + +# This welcomes the "person" passed to it. +def welcome(newcomer): + ircsock.send("PRIVMSG {0} :Welcome {1}! The channel is pretty quiet " + "right now, so I though I'd say hello, and ping some people " + "(like {2}) that you're here. If no one responds for a " + "while, try emailing us at hello@openhatch.org or just try " + "coming back later. FYI, you're now on my list of known " + "nicknames, so I won't bother you again." + "\n".format(channel, newcomer, channel_admins)) + + +# Adds the current NewComer's nick to nicks.csv and known_nicks. +def add_known_nick(new_known_nick): + new_known_nick = new_known_nick.replace("_", "") + known_nicks.append([new_known_nick]) with open('nicks.csv', 'a') as csvfile: - nickwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) - nickwriter.writerow([person]) + nickwriter = csv.writer(csvfile, delimiter=',', quotechar='|', + quoting=csv.QUOTE_MINIMAL) + nickwriter.writerow([new_known_nick]) -def getWelcomeRegEx(stringArray): + +# "I NEED NOTES!!!!", said the function. +def get_welcome_regex(string_array): #make regex case-insenstive pattern = r'(?i)' - for s in stringArray: + for s in string_array: pattern += r'(?:[ :]'+s+r'(?:[ \.!\?,\)]|$))|' #delete trailing '|' pattern = pattern[:-1] return pattern -# Startup -ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -ircsock.connect((server, 6667)) # Here we connect to the server using the port 6667 -ircsock.send("USER " + botnick + " " + botnick + " " + botnick + " :This is http://openhatch.org/'s greeter bot.\n") # user authentication -ircsock.send("NICK " + botnick + "\n") # here we actually assign the nick to the bot -joinchan(channel) -# Starts a separate thread to get messages from server -q = Queue.LifoQueue() -t = Thread(target=getIRC) # calls getIRC() (defined above) in a separate thread +# This function is used to change the wait time from the channel. +# It confirms that the attempt is allowed and then returns the requested value. +# If the attempt is not allowed, a message is sent to help +def wait_time_change(): + for admin in channel_admins: + if actor == admin: + finder = search(r'\d\d*', search(r'--wait-time \d\d*', ircmsg) + .group()) + ircsock.send("PRIVMSG {0} :{1} the wait time is changing to {2} " + "seconds.\n".format(channel, actor, finder.group())) + return finder.group() + ircsock.send("PRIVMSG {0} :{1} you are not authorized to make that " + "change. Please contact one of the channel admins {2} for " + "assistance.\n".format(channel, actor, channel_admins)) + + +#################### Startup #################### +# Creates a socket that will be used to send and receive messages, +# then connects the socket to an IRC server and joins the channel. +ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ircsock.connect((server, 6667)) # Here we connect to server using port 6667. +ircsock.send("USER {0} {0} {0} :This is http://openhatch.org/'s greeter bot" + ".\n".format(botnick)) # bot authentication +ircsock.send("NICK {}\n".format(botnick)) # Assign the nick to the bot. +join_channel(channel) + +# Creates a list of known nicks from nicks.csv. +# The list is checked to ensure that people are only greeted once. +# NewComers will be added to the list and the csv. +known_nicks = [] +with open('nicks.csv', 'rb') as csv_file: + csv_file_data = csv.reader(csv_file, delimiter=',', quotechar='|') + for row in csv_file_data: + known_nicks.append(row) + +# Creates a Queue that will hold the incoming messages. +q = Queue.Queue() +# Creates a separate thread that will listen on the server and add any +# messages to the Queue. The thread is created to run target which equals +# the msg_handler function from above. +t = Thread(target=msg_handler) t.daemon = True t.start() -nickArray = makeNickArray() +# This is the array of NewComer objects that people who join are added to. +newcomers = [] -newList = [] # This is the array of newcomer objects that people who join the room are added to. -helloArray = [r'hello',r'hi',r'hey',r'yo',r'sup'] -helpArray = [r'help',r'info',r'faq',r'explain yourself'] -helloPattern = getWelcomeRegEx(helloArray) -helpPattern = getWelcomeRegEx(helpArray) +hello_list = [r'hello', r'hi', r'hey', r'yo', r'sup'] +help_list = [r'help', r'info', r'faq', r'explain yourself'] +hello_pattern = get_welcome_regex(hello_list) +help_pattern = get_welcome_regex(help_list) -while 1: # Loop forever - for i in newList: - if i.status == 0 and i.aroundFor() > waitTime: +#################### The Workhorse #################### +# This is the main loop that monitors the channel and sends and receives the +# messages, either in the below code or by calling a function. This is the +# brain of this code. +while 1: # loop forever + + for i in newcomers: + if i.status == 0 and i.around_for() > wait_time: welcome(i.nick) - i.updateStatus() - addPerson(i.nick) - newList.remove(i) + i.update_status() + add_known_nick(i.nick) + newcomers.remove(i) + # If the queue is not empty... if q.empty() == 0: + # get the next msg in the queue ircmsg = q.get() + # and get the nick of the msg sender actor = ircmsg.split(":")[1].split("!")[0] - # Welcome functions - if ircmsg.find("PRIVMSG " + channel) != -1: # If someone has spoken into the channel - for i in newList: - if actor != i.nick: # Don't turn off response if the person speaking is the person who joined. - i.updateStatus() # Sets status to 1 - addPerson(i.nick) - newList.remove(i) - ## Else: Do we want to do something extra if the person who joined the chat says something with no response? + ##### Welcome functions ##### + # If someone has spoken into the channel... + if ircmsg.find("PRIVMSG " + channel) != -1: + for i in newcomers: + if actor != i.nick: # and is not the new NewComer + i.update_status() # set the status to 1 + add_known_nick(i.nick) + newcomers.remove(i) + ## Else: Do we want to do something extra if the person who + # joined the chat says something with no response? - if ircmsg.find("JOIN " + channel) != -1: # If someone joins #channel - if actor != botnick: # Remove the case where the bot gets a message that the bot has joined. - if [actor.replace("_","")] not in nickArray: - if actor not in (i.nick for i in newList): - newList.append(newcomer(actor)) + # If someone joins #channel... + if ircmsg.find("JOIN " + channel) != -1: + if actor != botnick: # and it is not the bot + if [actor.replace("_", "")] not in known_nicks: + if actor not in (i.nick for i in newcomers): + newcomers.append(NewComer(actor)) - if ircmsg.find("PART " + channel) != -1 or ircmsg.find("QUIT") != -1: # If someone parts or quits the #channel - for i in newList: # And that person is on the newlist (has entered channel within last 60 seconds) + # If someone parts or quits the #channel... + if ircmsg.find("PART " + channel) != -1 or ircmsg.find("QUIT") != -1: + for i in newcomers: # and that person is on the newlist if actor == i.nick: - newList.remove(i) # Remove them from the list + newcomers.remove(i) # remove them from the list - # Unwelcome functions - if ircmsg.find(botnick) != -1 and ircmsg.find("PRIVMSG") != -1: # If someone talks to (or refers to) the bot - chan = channel - matchHello = search(helloPattern,ircmsg) - matchHelp = search(helpPattern,ircmsg) - if ircmsg.find("PRIVMSG " + botnick) != -1: - chan = actor + ##### Unwelcome functions ##### + # If someone talks to (or refers to) the bot. + if ircmsg.find(botnick) != -1 and ircmsg.find("PRIVMSG") != -1: + matchHello = search(hello_pattern, ircmsg) + matchHelp = search(help_pattern, ircmsg) if matchHello: - hello(actor,random.choice(helloArray), chan) + bot_hello(random.choice(hello_list)) if matchHelp: - help(actor, chan) + bot_help() - if ircmsg.find("PING :") != -1: # if the server pings us then we've got to respond! - ping() + # If someone tries to change the wait time... + if ircmsg.find(change_wait) != -1: + wait_time = wait_time_change() # call this to check and change it + # If the server pings us then we've got to respond! + if ircmsg.find("PING :") != -1: + pong() From e92de19b2129af59080a18363dffabe5e656fd1c Mon Sep 17 00:00:00 2001 From: "james.bertino" Date: Sat, 3 May 2014 19:46:56 -0400 Subject: [PATCH 2/3] Minor tweeks to Issue #14 fix. --- bot.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/bot.py b/bot.py index b648dc4..6dbe777 100644 --- a/bot.py +++ b/bot.py @@ -15,7 +15,7 @@ from re import search server = "irc.freenode.net" channel = "#openhatch" botnick = 'WelcomeBot' -channel_admins = ('shauna', 'paulproteus', 'marktraceur') +channel_greeters = ['shauna', 'paulproteus', 'marktraceur'] wait_time = 60 # amount of time after joining before bot replies to someone change_wait = botnick + " --wait-time " @@ -66,19 +66,34 @@ def bot_hello(greeting): def bot_help(): ircsock.send("PRIVMSG {} :I'm a bot! I'm from here . You can change my behavior by " - "submitting a pull request or by talking to 'fill in " - "the blank'.\n".format(channel)) + "submitting a pull request or by talking to shauna" + ".\n".format(channel)) + + +# Returns a grammatically correct string of the channel_greeters. +def greeter_string(conjunction): + greeters = "" + if len(channel_greeters) > 2: + for name in channel_greeters[:-1]: + greeters += "{}, ".format(name) + greeters += "{0} {1}".format(conjunction, channel_greeters[-1]) + elif len(channel_greeters) == 2: + greeters = "{0} {1} {2}".format(channel_greeters[0], conjunction, + channel_greeters[1]) + else: + greeters = channel_greeters[0] + return greeters # This welcomes the "person" passed to it. def welcome(newcomer): ircsock.send("PRIVMSG {0} :Welcome {1}! The channel is pretty quiet " "right now, so I though I'd say hello, and ping some people " - "(like {2}) that you're here. If no one responds for a " + ",like {2}, that you're here. If no one responds for a " "while, try emailing us at hello@openhatch.org or just try " "coming back later. FYI, you're now on my list of known " "nicknames, so I won't bother you again." - "\n".format(channel, newcomer, channel_admins)) + "\n".format(channel, newcomer, greeter_string("and"))) # Adds the current NewComer's nick to nicks.csv and known_nicks. @@ -106,7 +121,7 @@ def get_welcome_regex(string_array): # It confirms that the attempt is allowed and then returns the requested value. # If the attempt is not allowed, a message is sent to help def wait_time_change(): - for admin in channel_admins: + for admin in channel_greeters: if actor == admin: finder = search(r'\d\d*', search(r'--wait-time \d\d*', ircmsg) .group()) @@ -114,8 +129,8 @@ def wait_time_change(): "seconds.\n".format(channel, actor, finder.group())) return finder.group() ircsock.send("PRIVMSG {0} :{1} you are not authorized to make that " - "change. Please contact one of the channel admins {2} for " - "assistance.\n".format(channel, actor, channel_admins)) + "change. Please contact one of the channel greeters, like {2}, for " + "assistance.\n".format(channel, actor, greeter_string("or"))) #################### Startup #################### From 4ad322b03087fb19257b4cab5bc16a0062350db3 Mon Sep 17 00:00:00 2001 From: "james.bertino" Date: Mon, 5 May 2014 17:57:46 -0400 Subject: [PATCH 3/3] Changed the return value of wait_time_change() to an int and updated the README. --- README.md | 2 +- bot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c0ec230..fab5bf8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ staying in touch bot.py is the project's main file. It uses the socket module to communicate and gathers a list of known IRC nicknames, stored in nicks.csv. Its basic functions include: -1. If someone enters the channel, the bot checks to see if they are a known nick. If not, it adds them to a list of people to greet. If no one else has spoken into the channel after 60 seconds, it greets them. If someone else (not the new nick) speaks into the channel within 60 seconds, or if the nick is known, the bot remains silent. The bots response text includes the nicks of channel maintainers/frequent contributors so that they are pinged. +1. If someone enters the channel, the bot checks to see if they are a known nick. If not, it adds them to a list of people to greet. If no one else has spoken into the channel after a period of time, 60 seconds by default, it greets them. If someone else (not the new nick) speaks into the channel within the set wait time, or if the nick is known, the bot remains silent. The bots response text includes the nicks of channel maintainers/frequent contributors so that they are pinged. Channel maintainers can change the wait time of the bot by using the following command in the irc channel: *Botname* --wait-time *new wait time in seconds*. 2. If someone says hello to the bot, the bot says hello back. 3. If someone asks the bot for information (via key phrases like "help", "faq", etc) the bot explains what it is and links to this repository. diff --git a/bot.py b/bot.py index 6dbe777..454a750 100644 --- a/bot.py +++ b/bot.py @@ -12,7 +12,7 @@ from threading import Thread from re import search # Some basic variables used to configure the bot. -server = "irc.freenode.net" +server = "irc.freenode.net" channel = "#openhatch" botnick = 'WelcomeBot' channel_greeters = ['shauna', 'paulproteus', 'marktraceur'] @@ -127,7 +127,7 @@ def wait_time_change(): .group()) ircsock.send("PRIVMSG {0} :{1} the wait time is changing to {2} " "seconds.\n".format(channel, actor, finder.group())) - return finder.group() + return int(finder.group()) ircsock.send("PRIVMSG {0} :{1} you are not authorized to make that " "change. Please contact one of the channel greeters, like {2}, for " "assistance.\n".format(channel, actor, greeter_string("or")))