home · contact · privacy
Some more refactoring / de-globalizing.
[plomlombot-irc.git] / plomlombot.py
1 import socket
2 import datetime
3 import select
4 import time
5 import re
6 import urllib.request
7 import html
8
9 SERVERNET = "irc.freenode.net"
10 PORT = 6667
11 TIMEOUT = 240
12 USERNAME = "plomlombot"
13 NICKNAME = USERNAME
14 CHANNEL = "#zrolaps-test"
15
16 class ExceptionForRestart(Exception):
17     pass
18
19 class IO:
20
21     def __init__(self, servernet, port, timeout):
22         self.timeout = timeout
23         self.socket = socket.socket()
24         self.socket.connect((servernet, port))
25         self.socket.setblocking(0)
26         self.line_buffer = []
27         self.rune_buffer = ""
28         self.last_pong = time.time()
29         self.servername = self.recv_line(send_ping=False).split(" ")[0][1:]
30
31     def _pingtest(self, send_ping=True):
32         if self.last_pong + self.timeout < time.time():
33             print("SERVER NOT ANSWERING")
34             raise ExceptionForRestart
35         if send_ping:
36             self.send_line("PING " + self.servername)
37
38     def send_line(self, msg):
39         msg = msg.replace("\r", " ")
40         msg = msg.replace("\n", " ")
41         if len(msg.encode("utf-8")) > 510:
42             print("NOT SENT LINE TO SERVER (too long): " + msg)
43         print("LINE TO SERVER: "
44             + str(datetime.datetime.now()) + ": " + msg)
45         msg = msg + "\r\n"
46         msg_len = len(msg)
47         total_sent_len = 0
48         while total_sent_len < msg_len:
49             sent_len = self.socket.send(bytes(msg[total_sent_len:], "UTF-8"))
50             if sent_len == 0:
51                 print("SOCKET CONNECTION BROKEN")
52                 raise ExceptionForRestart
53             total_sent_len += sent_len
54
55     def _recv_line_wrapped(self, send_ping=True):
56         if len(self.line_buffer) > 0:
57             return self.line_buffer.pop(0)
58         while True:
59             ready = select.select([self.socket], [], [], int(self.timeout / 2))
60             if not ready[0]:
61                 self._pingtest(send_ping)
62                 return None
63             self.last_pong = time.time()
64             received_runes = self.socket.recv(1024).decode("UTF-8")
65             if len(received_runes) == 0:
66                 print("SOCKET CONNECTION BROKEN")
67                 raise ExceptionForRestart
68             self.rune_buffer += received_runes 
69             lines_split = str.split(self.rune_buffer, "\r\n")
70             self.line_buffer += lines_split[:-1]
71             self.rune_buffer = lines_split[-1]
72             if len(self.line_buffer) > 0:
73                 return self.line_buffer.pop(0)
74
75     def recv_line(self, send_ping=True):
76         line = self._recv_line_wrapped(send_ping)
77         if line:
78             print("LINE FROM SERVER " + str(datetime.datetime.now()) + ": " +
79             line)
80         return line
81
82 def init_session(servernet, port, timeout, nickname, username, channel):
83     print("CONNECTING TO " + servernet)
84     io = IO(servernet, port, timeout)
85     io.send_line("NICK " + nickname)
86     io.send_line("USER " + username + " 0 * : ")
87     io.send_line("JOIN " + channel)
88     return io
89
90 def lineparser_loop(io, nickname):
91
92     def act_on_privmsg(tokens):
93
94         def url_check(msg):
95             matches = re.findall("(https?://[^\s]+)", msg)
96             for i in range(len(matches)):
97                 url = matches[i]
98                 try:
99                     webpage = urllib.request.urlopen(url, timeout=15)
100                 except urllib.error.HTTPError as error:
101                     print("TROUBLE FOLLOWING URL: " + str(error))
102                     continue
103                 charset = webpage.info().get_content_charset()
104                 if not charset:
105                     charset="utf-8"
106                 content_type = webpage.info().get_content_type()
107                 if not content_type in ('text/html', 'text/xml',
108                         'application/xhtml+xml'):
109                     print("TROUBLE INTERPRETING URL: bad content type "
110                             + content_type)
111                     continue
112                 content = webpage.read().decode(charset)
113                 title = str(content).split('<title>')[1].split('</title>')[0]
114                 title = html.unescape(title)
115                 io.send_line("PRIVMSG " + target + " :page title for url: "
116                     + title)
117
118         sender = ""
119         for rune in tokens[0]:
120             if rune == "!":
121                 break
122             if rune != ":":
123                 sender += rune
124         receiver = ""
125         for rune in tokens[2]:
126             if rune == "!":
127                 break
128             if rune != ":":
129                 receiver += rune
130         target = sender
131         if receiver != nickname:
132             target = receiver
133         msg = str.join(" ", tokens[3:])[1:]
134         url_check(msg)
135
136     while 1:
137         line = io.recv_line()
138         if not line:
139             continue
140         tokens = line.split(" ")
141         if len(tokens) > 1:
142             if tokens[1] == "PRIVMSG":
143                 act_on_privmsg(tokens)
144             if tokens[0] == "PING":
145                 io.send_line("PONG " + tokens[1])
146 while 1:
147     try:
148         io = init_session(SERVERNET, PORT, TIMEOUT, NICKNAME, USERNAME,
149                 CHANNEL)
150         lineparser_loop(io, NICKNAME)
151     except ExceptionForRestart:
152         io.socket.close()
153         continue