irc-skill/__init__.py
merspieler 34f7d53044 Added handling of return codes and closed connections
Signed-off-by: merspieler <merspieler@users.noreply.github.com>
2018-03-16 10:39:46 +01:00

384 lines
11 KiB
Python

from threading import Thread
from time import sleep
import select
import socket
import socks
import ssl
import re
from adapt.intent import IntentBuilder
from mycroft.audio import wait_while_speaking
from mycroft import MycroftSkill, intent_handler
from mycroft.skills.core import MycroftSkill
from mycroft.util.log import getLogger
from mycroft.util import normalize
#########################################
# RFC #
# https://tools.ietf.org/html/rfc1459 #
# We don't fullfil all aspects of the #
# RFC to keep it simple. If you think #
# a command is needed, feel free to #
# open a PR with your changes. #
#########################################
LOGGER = getLogger(__name__)
class IRCSkill(MycroftSkill):
def __init__(self):
super(IRCSkill, self).__init__()
# TODO make them configureable
# TODO make them into lists
# options
self.settings['proxy'] = ""
self.settings['proxy-port'] = 9050
self.settings['proxy-user'] = ""
self.settings['proxy-passwd'] = ""
self.settings['server'] = "irc.flightgear.org"
self.settings['server-password'] = ""
self.settings['port'] = 6667
self.settings['channel'] = "mycroft"
self.settings['channel-password'] = ""
self.settings['user'] = "dummy|m"
self.settings['password'] = ""
self.settings['ssl'] = False
self.settings['debug'] = False
# IPC for comunicating between threads
self.irc_lock = False
self.irc_cmd = ""
self.irc_str = ""
def initialize(self):
if self.settings['proxy'] != "":
if self.settings['debug']:
self.speak("Using proxy: " + self.settings['proxy'])
self.speak("Port: " + str(self.settings['proxy-port']))
socks.set_default_proxy(socks.SOCKS5, self.settings['proxy'], self.settings['proxy-port'], True, 'user','passwd')
socket.socket = socks.socksocket
self._irc_start_thread()
@intent_handler(IntentBuilder('ConnectIntent').require('connect'))
def handle_connect_intent(self, message):
# TODO ability to connect to different server
if self.con_thread.isAlive() == False:
self._irc_start_thread()
if self.irc_lock == False:
self.speak("Connecting")
self.irc_lock == True
self.irc_cmd = "connect"
self.irc_str = ""
self.irc_lock = False
@intent_handler(IntentBuilder('JoinIntent').require('join'))
def handle_join_intent(self, message):
# TODO ability to join to different channels
if self.con_thread.isAlive() == False:
self._irc_start_thread()
if self.irc_lock == False:
self.speak("Joining")
self.irc_lock == True
self.irc_cmd = "join"
self.irc_str = ""
self.irc_lock = False
@intent_handler(IntentBuilder('PartIntent').require('part'))
def handle_part_intent(self, message):
# TODO ability to join to different channels
if self.con_thread.isAlive() == False:
self._irc_start_thread()
if self.irc_lock == False:
self.speak("Parting")
self.irc_lock == True
self.irc_cmd = "part"
self.irc_str = ""
self.irc_lock = False
@intent_handler(IntentBuilder('DisconnectIntent').require('disconnect'))
def handle_disconnect_intent(self, message):
# TODO ability to disconnect from different server
if self.con_thread.isAlive() == False:
self._irc_start_thread()
if self.irc_lock == False:
self.speak("Disconnecting")
self.irc_lock == True
self.irc_cmd = "disconnect"
self.irc_str = ""
self.irc_lock = False
@intent_handler(IntentBuilder('SendIntent').require('send'))
def handle_send_intent(self, message):
# TODO ability to send to different users and channels
if self.con_thread.isAlive() == False:
self._irc_start_thread()
if self.irc_lock == False:
response = self.get_response("get_msg")
if response != None:
self.irc_lock == True
self.irc_cmd = "send"
self.irc_str = response
self.irc_lock = False
else:
self.speak("I didn't understand a message")
@intent_handler(IntentBuilder('DebugEnableIntent').require('debug-enable'))
def handle_debug_enable_intent(self, message):
self.settings['debug'] = True
self.speak("Debugging enabled")
@intent_handler(IntentBuilder('DebugDisableIntent').require('debug-disable'))
def handle_debug_disable_intent(self, message):
self.settings['debug'] = False
self.speak("Debugging disabled")
def _main_loop(self):
connected = False
joined = False
while True:
sleep(2)
if connected:
text = ""
try:
ready = select.select([irc], [], [], 2)
if ready[0]:
text = irc.recv(2040)
except Exception:
continue
for line in text.splitlines():
if line != "":
if self.settings['debug']:
self.speak(str(line))
pass
# Prevent Timeout
match = re.search("^PING (.*)$", line, re.M)
if match != None:
irc.send('PONG ' + match.group(1) + '\r\n')
# reciving normal messages
match = re.search("^:(.*)!.*@.* QUIT", line, re.M)
if match != None:
self.speak(match.group(1) + " has disconnected")
match = re.search("^:(.*)!.*@.* JOIN", line, re.M)
if match != None:
if match.group(1) != self.settings['user']:
self.speak(match.group(1) + " has joined the channel")
match = re.search("^:(.*)!.*@.* PART", line, re.M)
if match != None:
self.speak(match.group(1) + " has left the channel")
match = re.search("^:(.*)!.*@.* QUIT", line, re.M)
if match != None:
self.speak(match.group(1) + " has disconnected")
match = re.search("^:(.*)!.*@.* PRIVMSG #(.*) :(.*)", line, re.M)
if match != None:
self.speak(match.group(1) + " has written in " + match.group(2) + ": " + match.group(3))
match = re.search("^:(.*)!.*@.* NOTICE #.* :(.*)", line, re.M)
if match != None:
self.speak(match.group(1) + " has written a notice to " + match.group(2) + ". The notice is: " + match.group(3))
match = re.search("^:(.*)!.*@.* PRIVMSG " + re.escape(self.settings['user']) + " :(.*)$", line, re.M)
if match != None:
self.speak(line)
self.speak(match.group(1) + " has written you a private message: " + match.group(2))
match = re.search(":(.*)!.*@.* NOTICE " + re.escape(self.settings['user']) + " :(.*)", line)
if match != None:
self.speak(match.group(1) + " has written a private notice to you. The notice is: " + match.group(2))
# reciving status codes
match = re.search("^:(\S*\.*.\S*) (\d{3}) (.*)", line)
if match != None:
code = int(match.group(2))
if self.settings['debug']:
self.speak("Return code: " + str(code))
# This list of handles replies is incomplete
if code == 401:
self.speak("The nickname wasn't found")
if code == 433:
self.speak("Your nickname is already in use")
if code == 464:
self.speak("It looks, like you password is wrong. Please check it")
if code == 465:
self.speak("Your banned on this server")
# handling special messages
match = re.search("^ERROR :Closing link", line)
if match != None:
self.speak("The server has closed the connection")
connected = False
irc.close()
cmd = ""
string = ""
if self.irc_cmd != "":
if self.irc_lock == False:
self.irc_lock = True
cmd = self.irc_cmd
string = self.irc_str
self.irc_cmd = ""
self.irc_str = ""
self.irc_lock = False
if cmd != "":
# check cmd and take action
if cmd == "connect":
# TODO add ability to connect to more than one server
if connected == False:
connected, irc = self._irc_connect(self.settings['server'], self.settings['port'], self.settings['ssl'], self.settings['server-password'], self.settings['user'], self.settings['password'])
else:
self.speak("Already connected")
elif cmd == "join":
if connected:
joined = self._irc_join(irc, self.settings['channel'], self.settings['channel-password'])
else:
self.speak("Please connect to a server first")
elif cmd == "part":
if connected:
if joined:
joined = self._irc_part(irc, self.settings['channel'])
else:
self.speak("I'm in no channel I could part from")
else:
self.speak("Not connected to a server")
elif cmd == "disconnect":
if connected:
connected = self._irc_disconnect(irc)
else:
self.speak("I'm to no server connected")
if connected == False:
joined = False
elif cmd == "send":
if connected:
if joined:
self._irc_send(irc, "#" + self.settings['channel'], string)
else:
self.speak("Please join a channel first")
else:
self.speak("Please connect to a server and join a channel first")
def _irc_connect(self, server, port, ssl_req, server_password, user, password):
irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #defines the socket
# Connect
try:
if self.settings['debug']:
self.speak("Server: " + server)
self.speak("Port: " + str(port))
irc.settimeout(15)
irc.connect((server, port))
except Exception as e:
self.speak("Unable to connect to server.")
if self.settings['debug']:
self.speak("Error: " + str(e))
return False, irc
if ssl_req:
if self.settings['debug']:
self.speak("Use SSL")
irc = ssl.wrap_socket(irc)
irc.setblocking(0)
if server_password != "":
irc.send("PASS %s\n" % (password))
irc.send("USER " + user + " " + user + " " + user + " :IRC via VOICE -> Mycroft\n")
irc.send("NICK " + user + "\n")
if password != "":
irc.send("PRIVMSG nickserv :identify %s %s\r\n" % (user, password))
self.speak("Connected")
return True, irc
def _irc_join(self, irc, channel, channel_password):
# TODO add steps to be able to use a pw
irc.send("JOIN #"+ channel +"\n")
self.speak("Channel " + channel + " joined")
return True
def _irc_part(self, irc, channel):
irc.send("PART #" + channel)
self.speak("Parted")
return False # this is the value that's written in `joined`
def _irc_disconnect(self, irc):
irc.send("QUIT :Disconnected my mycroft\n")
irc.close()
self.speak("Disconnected")
return False # this is the value that's written in `connected`
def _irc_send(self, irc, to, msg):
irc.send("PRIVMSG " + to + " :" + msg + "\n")
self.speak("Message sent")
def _irc_start_thread(self):
if self.settings['debug']:
self.speak("Restart thread")
self.con_thread = Thread(target=self._main_loop)
self.con_thread.setDaemon(False)
self.con_thread.start()
def stop(self):
pass
def create_skill():
return IRCSkill()
"""
### Tail
tail_files = [
'/tmp/file-to-tail.txt'
]
print "Establishing connection to [%s]" % (server)
tail_line = []
for i, tail in enumerate(tail_files):
tail_line.append('')
while True:
time.sleep(2)
# Tail Files
for i, tail in enumerate(tail_files):
try:
f = open(tail, 'r')
line = f.readlines()[-1]
f.close()
if tail_line[i] != line:
tail_line[i] = line
irc.send("PRIVMSG %s :%s" % (channel, line))
except Exception as e:
print "Error with file %s" % (tail)
print e
try:
text=irc.recv(2040)
print text
# Prevent Timeout
if text.find('PING') != -1:
irc.send('PONG ' + text.split() [1] + '\r\n')
except Exception:
continue
"""