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