16 # Defaults, may be overwritten by command line arguments.
17 SERVER = "irc.freenode.net"
20 USERNAME = "plomlombot"
24 class ExceptionForRestart(Exception):
30 def __init__(self, server, port, timeout):
31 self.timeout = timeout
32 self.socket = socket.socket()
33 self.socket.connect((server, port))
34 self.socket.setblocking(0)
37 self.last_pong = time.time()
38 self.servername = self.recv_line(send_ping=False).split(" ")[0][1:]
40 def _pingtest(self, send_ping=True):
41 if self.last_pong + self.timeout < time.time():
42 print("SERVER NOT ANSWERING")
43 raise ExceptionForRestart
45 self.send_line("PING " + self.servername)
47 def send_line(self, msg):
48 msg = msg.replace("\r", " ")
49 msg = msg.replace("\n", " ")
50 if len(msg.encode("utf-8")) > 510:
51 print("NOT SENT LINE TO SERVER (too long): " + msg)
52 print("LINE TO SERVER: "
53 + str(datetime.datetime.now()) + ": " + msg)
57 while total_sent_len < msg_len:
58 sent_len = self.socket.send(bytes(msg[total_sent_len:], "UTF-8"))
60 print("SOCKET CONNECTION BROKEN")
61 raise ExceptionForRestart
62 total_sent_len += sent_len
64 def _recv_line_wrapped(self, send_ping=True):
65 if len(self.line_buffer) > 0:
66 return self.line_buffer.pop(0)
68 ready = select.select([self.socket], [], [], int(self.timeout / 2))
70 self._pingtest(send_ping)
72 self.last_pong = time.time()
73 received_bytes = self.socket.recv(1024)
75 received_runes = received_bytes.decode("UTF-8")
76 except UnicodeDecodeError:
77 received_runes = received_bytes.decode("latin1")
78 if len(received_runes) == 0:
79 print("SOCKET CONNECTION BROKEN")
80 raise ExceptionForRestart
81 self.rune_buffer += received_runes
82 lines_split = str.split(self.rune_buffer, "\r\n")
83 self.line_buffer += lines_split[:-1]
84 self.rune_buffer = lines_split[-1]
85 if len(self.line_buffer) > 0:
86 return self.line_buffer.pop(0)
88 def recv_line(self, send_ping=True):
89 line = self._recv_line_wrapped(send_ping)
91 print("LINE FROM SERVER " + str(datetime.datetime.now()) + ": " +
96 def init_session(server, port, timeout, nickname, username, channel):
97 print("CONNECTING TO " + server)
98 io = IO(server, port, timeout)
99 io.send_line("NICK " + nickname)
100 io.send_line("USER " + username + " 0 * : ")
101 io.send_line("JOIN " + channel)
105 def lineparser_loop(io, nickname):
107 def act_on_privmsg(tokens):
110 io.send_line("NOTICE " + target + " :" + msg)
114 def handle_url(url, show_url=False):
116 def mobile_twitter_hack(url):
117 re1 = 'https?://(mobile.twitter.com/)[^/]+(/status/)'
118 re2 = 'https?://mobile.twitter.com/([^/]+)/status/' \
120 m = re.search(re1, url)
121 if m and m.group(1) == 'mobile.twitter.com/' \
122 and m.group(2) == '/status/':
123 m = re.search(re2, url)
124 url = 'https://twitter.com/' + m.group(1) + '/status/' \
126 handle_url(url, True)
130 r = requests.get(url, timeout=15)
131 except (requests.exceptions.TooManyRedirects,
132 requests.exceptions.ConnectionError,
133 requests.exceptions.InvalidURL,
134 requests.exceptions.InvalidSchema) as error:
135 notice("TROUBLE FOLLOWING URL: " + str(error))
137 if mobile_twitter_hack(url):
139 title = bs4.BeautifulSoup(r.text).title
141 prefix = "PAGE TITLE: "
143 prefix = "PAGE TITLE FOR <" + url + ">: "
144 notice(prefix + title.string.strip())
146 notice("PAGE HAS NO TITLE TAG")
148 matches = re.findall("(https?://[^\s>]+)", msg)
149 for i in range(len(matches)):
150 handle_url(matches[i])
152 def command_check(msg):
155 tokens = msg[1:].split()
156 hash_string = hashlib.md5(target.encode("utf-8")).hexdigest()
157 quotesfile_name = "quotes_" + hash_string
158 if tokens[0] == "addquote":
159 if not os.access(quotesfile_name, os.F_OK):
160 quotesfile = open(quotesfile_name, "w")
161 quotesfile.write("QUOTES FOR " + target + ":\n")
163 quotesfile = open(quotesfile_name, "a")
164 quotesfile.write(str.join(" ", tokens[1:]) + "\n")
166 quotesfile = open(quotesfile_name, "r")
167 lines = quotesfile.readlines()
169 notice("ADDED QUOTE #" + str(len(lines) - 1))
170 elif tokens[0] == "quote":
171 if (len(tokens) > 2 and tokens[1] != "search") or \
172 (len(tokens) < 3 and tokens[1] == "search") or \
173 (len(tokens) == 2 and not tokens[1].isdigit()):
174 notice("SYNTAX: !quote [int] OR !quote search QUERY")
175 notice("QUERY may be a boolean grouping of quoted or "\
176 + "unquoted search terms, examples:")
177 notice("!quote search foo")
178 notice("!quote search foo AND (bar OR NOT baz)")
179 notice("!quote search \"foo\\\"bar\" AND "\
180 + "('NOT\"' AND \"'foo'\" OR 'bar\\'baz')")
182 if not os.access(quotesfile_name, os.F_OK):
183 notice("NO QUOTES AVAILABLE")
185 quotesfile = open(quotesfile_name, "r")
186 lines = quotesfile.readlines()
191 if i == 0 or i > len(lines):
192 notice("THERE'S NO QUOTE OF THAT INDEX")
195 elif len(tokens) > 2:
196 query = str.join(" ", tokens[2:])
198 results = plomsearch.search(query, lines)
199 except plomsearch.LogicParserError as err:
200 notice("FAILED QUERY PARSING: " + str(err))
202 if len(results) == 0:
203 notice("NO QUOTES MATCHING QUERY")
205 for result in results:
206 notice("QUOTE #" + str(result[0] + 1) + " : "
210 i = random.randrange(len(lines))
211 notice("QUOTE #" + str(i + 1) + ": " + lines[i])
214 for rune in tokens[0]:
220 for rune in tokens[2]:
226 if receiver != nickname:
228 msg = str.join(" ", tokens[3:])[1:]
233 line = io.recv_line()
236 tokens = line.split(" ")
238 if tokens[1] == "PRIVMSG":
239 act_on_privmsg(tokens)
240 if tokens[0] == "PING":
241 io.send_line("PONG " + tokens[1])
244 def parse_command_line_arguments():
245 parser = argparse.ArgumentParser()
246 parser.add_argument("-s, --server", action="store", dest="server",
248 help="server or server net to connect to (default: "
250 parser.add_argument("-p, --port", action="store", dest="port", type=int,
251 default=PORT, help="port to connect to (default : "
253 parser.add_argument("-t, --timeout", action="store", dest="timeout",
254 type=int, default=TIMEOUT,
255 help="timeout in seconds after which to attempt " +
256 "reconnect (default: " + str(TIMEOUT) + ")")
257 parser.add_argument("-u, --username", action="store", dest="username",
258 default=USERNAME, help="username to use (default: "
260 parser.add_argument("-n, --nickname", action="store", dest="nickname",
261 default=NICKNAME, help="nickname to use (default: "
263 parser.add_argument("CHANNEL", action="store", help="channel to join")
264 opts, unknown = parser.parse_known_args()
267 opts = parse_command_line_arguments()
270 io = init_session(opts.server, opts.port, opts.timeout, opts.nickname,
271 opts.username, opts.CHANNEL)
272 lineparser_loop(io, opts.nickname)
273 except ExceptionForRestart: