eric7/Network/IRC/IrcWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8268
6b8128e0c9d1
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the IRC window.
8 """
9
10 import re
11 import logging
12
13 from PyQt5.QtCore import (
14 pyqtSlot, pyqtSignal, Qt, QByteArray, QTimer, QDateTime
15 )
16 from PyQt5.QtWidgets import QWidget, QToolButton, QLabel, QTabWidget
17 from PyQt5.QtNetwork import QTcpSocket, QAbstractSocket
18 try:
19 from PyQt5.QtNetwork import QSslSocket, QSslConfiguration
20 from E5Network.E5SslErrorHandler import E5SslErrorHandler, E5SslErrorState
21 SSL_AVAILABLE = True
22 except ImportError:
23 SSL_AVAILABLE = False
24
25 from E5Gui import E5MessageBox
26
27 from .Ui_IrcWidget import Ui_IrcWidget
28
29 import Preferences
30 import UI.PixmapCache
31
32 from Globals import isMacPlatform
33
34 from UI.Info import Version, Copyright
35
36
37 class IrcWidget(QWidget, Ui_IrcWidget):
38 """
39 Class implementing the IRC window.
40
41 @signal autoConnected() emitted after an automatic connection was initiated
42 """
43 autoConnected = pyqtSignal()
44
45 ServerDisconnected = 1
46 ServerConnected = 2
47 ServerConnecting = 3
48
49 def __init__(self, parent=None):
50 """
51 Constructor
52
53 @param parent reference to the parent widget (QWidget)
54 """
55 super().__init__(parent)
56 self.setupUi(self)
57
58 from .IrcNetworkManager import IrcNetworkManager
59 self.__ircNetworkManager = IrcNetworkManager(self)
60
61 self.__leaveButton = QToolButton(self)
62 self.__leaveButton.setIcon(
63 UI.PixmapCache.getIcon("ircCloseChannel"))
64 self.__leaveButton.setToolTip(
65 self.tr("Press to leave the current channel"))
66 self.__leaveButton.clicked.connect(self.__leaveChannel)
67 self.__leaveButton.setEnabled(False)
68 self.channelsWidget.setCornerWidget(
69 self.__leaveButton, Qt.Corner.BottomRightCorner)
70 self.channelsWidget.setTabsClosable(False)
71 if not isMacPlatform():
72 self.channelsWidget.setTabPosition(QTabWidget.TabPosition.South)
73
74 height = self.height()
75 self.splitter.setSizes([height * 0.6, height * 0.4])
76
77 self.__channelList = []
78 self.__channelTypePrefixes = ""
79 self.__userName = ""
80 self.__identityName = ""
81 self.__quitMessage = ""
82 self.__nickIndex = -1
83 self.__nickName = ""
84 self.__server = None
85 self.__registering = False
86
87 self.__connectionState = IrcWidget.ServerDisconnected
88 self.__sslErrorLock = False
89
90 self.__buffer = ""
91 self.__userPrefix = {}
92
93 self.__socket = None
94 if SSL_AVAILABLE:
95 self.__sslErrorHandler = E5SslErrorHandler(self)
96 else:
97 self.__sslErrorHandler = None
98
99 self.__patterns = [
100 # :foo_!n=foo@foohost.bar.net PRIVMSG bar_ :some long message
101 (re.compile(r":([^!]+)!([^ ]+)\sPRIVMSG\s([^ ]+)\s:(.*)"),
102 self.__query),
103 # :foo.bar.net COMMAND some message
104 (re.compile(r""":([^ ]+)\s+([A-Z]+)\s+(.+)"""),
105 self.__handleNamedMessage),
106 # :foo.bar.net 123 * :info
107 (re.compile(r""":([^ ]+)\s+(\d{3})\s+(.+)"""),
108 self.__handleNumericMessage),
109 # PING :ping message
110 (re.compile(r"""PING\s+:(.*)"""), self.__ping),
111 ]
112 self.__prefixRe = re.compile(r""".*\sPREFIX=\((.*)\)([^ ]+).*""")
113 self.__chanTypesRe = re.compile(r""".*\sCHANTYPES=([^ ]+).*""")
114
115 ircPic = UI.PixmapCache.getPixmap("irc128")
116 self.__emptyLabel = QLabel()
117 self.__emptyLabel.setPixmap(ircPic)
118 self.__emptyLabel.setAlignment(
119 Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter)
120 self.channelsWidget.addTab(self.__emptyLabel, "")
121
122 # all initialized, do connections now
123 self.__ircNetworkManager.dataChanged.connect(self.__networkDataChanged)
124 self.networkWidget.initialize(self.__ircNetworkManager)
125 self.networkWidget.connectNetwork.connect(self.__connectNetwork)
126 self.networkWidget.editNetwork.connect(self.__editNetwork)
127 self.networkWidget.joinChannel.connect(self.joinChannel)
128 self.networkWidget.nickChanged.connect(self.__changeNick)
129 self.networkWidget.sendData.connect(self.__send)
130 self.networkWidget.away.connect(self.__away)
131 self.networkWidget.autoConnected.connect(self.autoConnected)
132
133 def shutdown(self):
134 """
135 Public method to shut down the widget.
136
137 @return flag indicating successful shutdown (boolean)
138 """
139 if self.__server:
140 if Preferences.getIrc("AskOnShutdown"):
141 ok = E5MessageBox.yesNo(
142 self,
143 self.tr("Disconnect from Server"),
144 self.tr(
145 """<p>Do you really want to disconnect from"""
146 """ <b>{0}</b>?</p><p>All channels will be closed."""
147 """</p>""").format(self.__server.getName()))
148 else:
149 ok = True
150 if ok:
151 self.__connectNetwork("", False, True)
152 else:
153 ok = True
154
155 if ok:
156 self.__ircNetworkManager.close()
157
158 return ok
159
160 def autoConnect(self):
161 """
162 Public method to initiate the IRC auto connection.
163 """
164 self.networkWidget.autoConnect()
165
166 def __connectNetwork(self, name, connect, silent):
167 """
168 Private slot to connect to or disconnect from the given network.
169
170 @param name name of the network to connect to (string)
171 @param connect flag indicating to connect (boolean)
172 @param silent flag indicating a silent connect/disconnect (boolean)
173 """
174 if connect:
175 network = self.__ircNetworkManager.getNetwork(name)
176 if network:
177 self.__server = network.getServer()
178 self.__identityName = network.getIdentityName()
179 identity = self.__ircNetworkManager.getIdentity(
180 self.__identityName)
181 if identity:
182 self.__userName = identity.getIdent()
183 self.__quitMessage = identity.getQuitMessage()
184 if self.__server:
185 useSSL = self.__server.useSSL()
186 if useSSL and not SSL_AVAILABLE:
187 E5MessageBox.critical(
188 self,
189 self.tr("SSL Connection"),
190 self.tr(
191 """An encrypted connection to the IRC"""
192 """ network was requested but SSL is not"""
193 """ available. Please change the server"""
194 """ configuration."""))
195 return
196
197 if useSSL:
198 # create SSL socket
199 self.__socket = QSslSocket(self)
200 self.__socket.encrypted.connect(
201 self.__hostConnected)
202 self.__socket.sslErrors.connect(
203 self.__sslErrors)
204 else:
205 # create TCP socket
206 self.__socket = QTcpSocket(self)
207 self.__socket.connected.connect(
208 self.__hostConnected)
209 self.__socket.hostFound.connect(
210 self.__hostFound)
211 self.__socket.disconnected.connect(
212 self.__hostDisconnected)
213 self.__socket.readyRead.connect(
214 self.__readyRead)
215 self.__socket.error.connect(
216 self.__tcpError)
217
218 self.__connectionState = IrcWidget.ServerConnecting
219 if useSSL:
220 self.networkWidget.addServerMessage(
221 self.tr("Info"),
222 self.tr("Looking for server {0} (port {1})"
223 " using an SSL encrypted connection"
224 "...").format(self.__server.getName(),
225 self.__server.getPort()))
226 self.__socket.connectToHostEncrypted(
227 self.__server.getName(),
228 self.__server.getPort()
229 )
230 else:
231 self.networkWidget.addServerMessage(
232 self.tr("Info"),
233 self.tr(
234 "Looking for server {0} (port {1})...")
235 .format(
236 self.__server.getName(),
237 self.__server.getPort()))
238 self.__socket.connectToHost(
239 self.__server.getName(),
240 self.__server.getPort())
241 else:
242 if silent:
243 ok = True
244 else:
245 ok = E5MessageBox.yesNo(
246 self,
247 self.tr("Disconnect from Server"),
248 self.tr("""<p>Do you really want to disconnect from"""
249 """ <b>{0}</b>?</p><p>All channels will be"""
250 """ closed.</p>""")
251 .format(self.__server.getName()))
252 if ok:
253 if self.__server is not None:
254 self.networkWidget.addServerMessage(
255 self.tr("Info"),
256 self.tr("Disconnecting from server {0}...").format(
257 self.__server.getName()))
258 elif name:
259 self.networkWidget.addServerMessage(
260 self.tr("Info"),
261 self.tr("Disconnecting from network {0}...").format(
262 name))
263 else:
264 self.networkWidget.addServerMessage(
265 self.tr("Info"),
266 self.tr("Disconnecting from server."))
267 self.__closeAllChannels()
268 self.__send("QUIT :" + self.__quitMessage)
269 if self.__socket:
270 self.__socket.flush()
271 self.__socket.close()
272 if self.__socket:
273 # socket is still existing
274 self.__socket.deleteLater()
275 self.__socket = None
276 self.__userName = ""
277 self.__identityName = ""
278 self.__quitMessage = ""
279
280 def __editNetwork(self, name):
281 """
282 Private slot to edit the network configuration.
283
284 @param name name of the network to edit (string)
285 """
286 from .IrcNetworkListDialog import IrcNetworkListDialog
287 dlg = IrcNetworkListDialog(self.__ircNetworkManager, self)
288 dlg.exec()
289
290 def __networkDataChanged(self):
291 """
292 Private slot handling changes of the network and identity definitions.
293 """
294 identity = self.__ircNetworkManager.getIdentity(self.__identityName)
295 if identity:
296 partMsg = identity.getPartMessage()
297 for channel in self.__channelList:
298 channel.setPartMessage(partMsg)
299
300 def joinChannel(self, name, key=""):
301 """
302 Public slot to join a channel.
303
304 @param name name of the channel (string)
305 @param key key of the channel (string)
306 """
307 # step 1: check, if this channel is already joined
308 for channel in self.__channelList:
309 if channel.name() == name:
310 return
311
312 from .IrcChannelWidget import IrcChannelWidget
313 channel = IrcChannelWidget(self)
314 channel.setName(name)
315 channel.setUserName(self.__nickName)
316 identity = self.__ircNetworkManager.getIdentity(self.__identityName)
317 if identity:
318 channel.setPartMessage(identity.getPartMessage())
319 channel.setUserPrivilegePrefix(self.__userPrefix)
320 channel.initAutoWho()
321
322 channel.sendData.connect(self.__send)
323 channel.sendCtcpRequest.connect(self.__sendCtcpRequest)
324 channel.sendCtcpReply.connect(self.__sendCtcpReply)
325 channel.channelClosed.connect(self.__closeChannel)
326 channel.openPrivateChat.connect(self.__openPrivate)
327 channel.awayCommand.connect(self.networkWidget.handleAwayCommand)
328 channel.leaveChannels.connect(self.__leaveChannels)
329 channel.leaveAllChannels.connect(self.__leaveAllChannels)
330
331 self.channelsWidget.addTab(channel, name)
332 self.__channelList.append(channel)
333 self.channelsWidget.setCurrentWidget(channel)
334
335 joinCommand = ["JOIN", name]
336 if key:
337 joinCommand.append(key)
338 self.__send(" ".join(joinCommand))
339 self.__send("MODE " + name)
340
341 emptyIndex = self.channelsWidget.indexOf(self.__emptyLabel)
342 if emptyIndex > -1:
343 self.channelsWidget.removeTab(emptyIndex)
344 self.__leaveButton.setEnabled(True)
345 self.channelsWidget.setTabsClosable(True)
346
347 def __query(self, match):
348 """
349 Private method to handle a new private connection.
350
351 @param match reference to the match object
352 @return flag indicating, if the message was handled (boolean)
353 """
354 # group(1) sender user name
355 # group(2) sender user@host
356 # group(3) target nick
357 # group(4) message
358 if match.group(4).startswith("\x01"):
359 return self.__handleCtcp(match)
360
361 self.__openPrivate(match.group(1))
362 # the above call sets the new channel as the current widget
363 channel = self.channelsWidget.currentWidget()
364 channel.addMessage(match.group(1), match.group(4))
365 channel.setPrivateInfo(
366 "{0} - {1}".format(match.group(1), match.group(2)))
367
368 return True
369
370 @pyqtSlot(str)
371 def __openPrivate(self, name):
372 """
373 Private slot to open a private chat with the given user.
374
375 @param name name of the user (string)
376 """
377 from .IrcChannelWidget import IrcChannelWidget
378 channel = IrcChannelWidget(self)
379 channel.setName(self.__nickName)
380 channel.setUserName(self.__nickName)
381 identity = self.__ircNetworkManager.getIdentity(self.__identityName)
382 if identity:
383 channel.setPartMessage(identity.getPartMessage())
384 channel.setUserPrivilegePrefix(self.__userPrefix)
385 channel.setPrivate(True, name)
386 channel.addUsers([name, self.__nickName])
387
388 channel.sendData.connect(self.__send)
389 channel.sendCtcpRequest.connect(self.__sendCtcpRequest)
390 channel.sendCtcpReply.connect(self.__sendCtcpReply)
391 channel.channelClosed.connect(self.__closeChannel)
392 channel.awayCommand.connect(self.networkWidget.handleAwayCommand)
393 channel.leaveChannels.connect(self.__leaveChannels)
394 channel.leaveAllChannels.connect(self.__leaveAllChannels)
395
396 self.channelsWidget.addTab(channel, name)
397 self.__channelList.append(channel)
398 self.channelsWidget.setCurrentWidget(channel)
399
400 @pyqtSlot()
401 def __leaveChannel(self):
402 """
403 Private slot to leave a channel and close the associated tab.
404 """
405 channel = self.channelsWidget.currentWidget()
406 channel.requestLeave()
407
408 @pyqtSlot(list)
409 def __leaveChannels(self, channelNames):
410 """
411 Private slot to leave a list of channels and close their associated
412 tabs.
413
414 @param channelNames list of channels to leave
415 @type list of str
416 """
417 for channelName in channelNames:
418 for channel in self.__channelList:
419 if channel.name() == channelName:
420 channel.leaveChannel()
421
422 @pyqtSlot()
423 def __leaveAllChannels(self):
424 """
425 Private slot to leave all channels and close their tabs.
426 """
427 while self.__channelList:
428 channel = self.__channelList[0]
429 channel.leaveChannel()
430
431 def __closeAllChannels(self):
432 """
433 Private method to close all channels.
434 """
435 while self.__channelList:
436 channel = self.__channelList.pop()
437 self.channelsWidget.removeTab(self.channelsWidget.indexOf(channel))
438 channel.deleteLater()
439 channel = None
440
441 self.channelsWidget.addTab(self.__emptyLabel, "")
442 self.__emptyLabel.show()
443 self.__leaveButton.setEnabled(False)
444 self.channelsWidget.setTabsClosable(False)
445
446 def __closeChannel(self, name):
447 """
448 Private slot handling the closing of a channel.
449
450 @param name name of the closed channel (string)
451 """
452 for channel in self.__channelList:
453 if channel.name() == name:
454 self.channelsWidget.removeTab(
455 self.channelsWidget.indexOf(channel))
456 self.__channelList.remove(channel)
457 channel.deleteLater()
458
459 if self.channelsWidget.count() == 0:
460 self.channelsWidget.addTab(self.__emptyLabel, "")
461 self.__emptyLabel.show()
462 self.__leaveButton.setEnabled(False)
463 self.channelsWidget.setTabsClosable(False)
464
465 @pyqtSlot(int)
466 def on_channelsWidget_tabCloseRequested(self, index):
467 """
468 Private slot to close a channel by pressing the close button of
469 the channels widget.
470
471 @param index index of the tab to be closed (integer)
472 """
473 channel = self.channelsWidget.widget(index)
474 channel.requestLeave()
475
476 def __send(self, data):
477 """
478 Private slot to send data to the IRC server.
479
480 @param data data to be sent (string)
481 """
482 if self.__socket:
483 self.__socket.write(
484 QByteArray("{0}\r\n".format(data).encode("utf-8")))
485
486 def __sendCtcpRequest(self, receiver, request, arguments):
487 """
488 Private slot to send a CTCP request.
489
490 @param receiver nick name of the receiver
491 @type str
492 @param request CTCP request to be sent
493 @type str
494 @param arguments arguments to be sent
495 @type str
496 """
497 request = request.upper()
498 if request == "PING":
499 arguments = "Eric IRC {0}".format(
500 QDateTime.currentMSecsSinceEpoch())
501
502 self.__send("PRIVMSG {0} :\x01{1} {2}\x01".format(
503 receiver, request, arguments))
504
505 def __sendCtcpReply(self, receiver, text):
506 """
507 Private slot to send a CTCP reply.
508
509 @param receiver nick name of the receiver
510 @type str
511 @param text text to be sent
512 @type str
513 """
514 self.__send("NOTICE {0} :\x01{1}\x01".format(receiver, text))
515
516 def __hostFound(self):
517 """
518 Private slot to indicate the host was found.
519 """
520 self.networkWidget.addServerMessage(
521 self.tr("Info"),
522 self.tr("Server found,connecting..."))
523
524 def __hostConnected(self):
525 """
526 Private slot to log in to the server after the connection was
527 established.
528 """
529 self.networkWidget.addServerMessage(
530 self.tr("Info"),
531 self.tr("Connected,logging in..."))
532 self.networkWidget.setConnected(True)
533
534 self.__registering = True
535 serverPassword = self.__server.getPassword()
536 if serverPassword:
537 self.__send("PASS " + serverPassword)
538
539 identity = self.__ircNetworkManager.getIdentity(
540 self.__identityName)
541 nick = self.networkWidget.getNickname()
542 if not nick and identity:
543 self.__nickIndex = 0
544 try:
545 nick = identity.getNickNames()[self.__nickIndex]
546 except IndexError:
547 nick = ""
548 if not nick:
549 nick = self.__userName
550 self.__nickName = nick
551 self.networkWidget.setNickName(nick)
552 if identity:
553 realName = identity.getRealName()
554 if not realName:
555 realName = "eric IDE chat"
556 self.__send("NICK " + nick)
557 self.__send("USER " + self.__userName + " 0 * :" + realName)
558
559 def __hostDisconnected(self):
560 """
561 Private slot to indicate the host was disconnected.
562 """
563 if self.networkWidget.isConnected():
564 self.__closeAllChannels()
565 self.networkWidget.addServerMessage(
566 self.tr("Info"),
567 self.tr("Server disconnected."))
568 self.networkWidget.setRegistered(False)
569 self.networkWidget.setConnected(False)
570 self.__server = None
571 self.__nickName = ""
572 self.__nickIndex = -1
573 self.__channelTypePrefixes = ""
574
575 if self.__socket:
576 self.__socket.deleteLater()
577 self.__socket = None
578
579 self.__connectionState = IrcWidget.ServerDisconnected
580 self.__sslErrorLock = False
581
582 def __readyRead(self):
583 """
584 Private slot to read data from the socket.
585 """
586 if self.__socket:
587 self.__buffer += str(
588 self.__socket.readAll(),
589 Preferences.getSystem("IOEncoding"),
590 'replace')
591 if self.__buffer.endswith("\r\n"):
592 for line in self.__buffer.splitlines():
593 line = line.strip()
594 if line:
595 logging.debug("<IRC> %s", line)
596 handled = False
597 # step 1: give channels a chance to handle the message
598 for channel in self.__channelList:
599 handled = channel.handleMessage(line)
600 if handled:
601 break
602 else:
603 # step 2: try to process the message ourselves
604 for patternRe, patternFunc in self.__patterns:
605 match = patternRe.match(line)
606 if match is not None and patternFunc(match):
607 break
608 else:
609 # Oops, the message wasn't handled
610 self.networkWidget.addErrorMessage(
611 self.tr("Message Error"),
612 self.tr(
613 "Unknown message received from server:"
614 "<br/>{0}").format(line))
615
616 self.__updateUsersCount()
617 self.__buffer = ""
618
619 def __handleCtcpReply(self, match):
620 """
621 Private method to handle a server message containing a CTCP reply.
622
623 @param match reference to the match object
624 """
625 if "!" in match.group(1):
626 sender = match.group(1).split("!", 1)[0]
627
628 try:
629 ctcpCommand = match.group(3).split(":", 1)[1]
630 except IndexError:
631 ctcpCommand = match.group(3)
632 ctcpCommand = ctcpCommand[1:].split("\x01", 1)[0]
633 if " " in ctcpCommand:
634 ctcpReply, ctcpArg = ctcpCommand.split(" ", 1)
635 else:
636 ctcpReply, ctcpArg = ctcpCommand, ""
637 ctcpReply = ctcpReply.upper()
638
639 if ctcpReply == "PING" and ctcpArg.startswith("Eric IRC "):
640 # it is a response to a ping request
641 pingDateTime = int(ctcpArg.split()[-1])
642 latency = QDateTime.currentMSecsSinceEpoch() - pingDateTime
643 self.networkWidget.addServerMessage(
644 self.tr("CTCP"),
645 self.tr(
646 "Received CTCP-PING response from {0} with latency"
647 " of {1} ms.").format(sender, latency))
648 else:
649 self.networkWidget.addServerMessage(
650 self.tr("CTCP"),
651 self.tr(
652 "Received unknown CTCP-{0} response from {1}.")
653 .format(ctcpReply, sender))
654
655 def __handleNamedMessage(self, match):
656 """
657 Private method to handle a server message containing a message name.
658
659 @param match reference to the match object
660 @return flag indicating, if the message was handled (boolean)
661 """
662 name = match.group(2)
663 if name == "NOTICE":
664 try:
665 msg = match.group(3).split(":", 1)[1]
666 except IndexError:
667 msg = match.group(3)
668
669 if msg.startswith("\x01"):
670 self.__handleCtcpReply(match)
671 return True
672
673 if "!" in match.group(1):
674 name = match.group(1).split("!", 1)[0]
675 msg = "-{0}- {1}".format(name, msg)
676 self.networkWidget.addServerMessage(self.tr("Notice"), msg)
677 return True
678 elif name == "MODE":
679 self.__registering = False
680 if ":" in match.group(3):
681 # :foo MODE foo :+i
682 name, modes = match.group(3).split(" :")
683 sourceNick = match.group(1)
684 if (
685 not self.isChannelName(name) and
686 name == self.__nickName
687 ):
688 if sourceNick == self.__nickName:
689 msg = self.tr(
690 "You have set your personal modes to"
691 " <b>[{0}]</b>.").format(modes)
692 else:
693 msg = self.tr(
694 "{0} has changed your personal modes to"
695 " <b>[{1}]</b>.").format(sourceNick, modes)
696 self.networkWidget.addServerMessage(
697 self.tr("Mode"), msg, filterMsg=False)
698 return True
699 elif name == "PART":
700 nick = match.group(1).split("!", 1)[0]
701 if nick == self.__nickName:
702 channel = match.group(3).split(None, 1)[0]
703 self.networkWidget.addMessage(
704 self.tr("You have left channel {0}.").format(channel))
705 return True
706 elif name == "QUIT":
707 # don't do anything with it here
708 return True
709 elif name == "NICK":
710 # :foo_!n=foo@foohost.bar.net NICK :newnick
711 oldNick = match.group(1).split("!", 1)[0]
712 newNick = match.group(3).split(":", 1)[1]
713 if oldNick == self.__nickName:
714 self.networkWidget.addMessage(
715 self.tr("You are now known as {0}.").format(newNick))
716 self.__nickName = newNick
717 self.networkWidget.setNickName(newNick)
718 else:
719 self.networkWidget.addMessage(
720 self.tr("User {0} is now known as {1}.").format(
721 oldNick, newNick))
722 return True
723 elif name == "PONG":
724 nick = match.group(3).split(":", 1)[1]
725 self.networkWidget.addMessage(
726 self.tr("Received PONG from {0}").format(nick))
727 return True
728 elif name == "ERROR":
729 self.networkWidget.addErrorMessage(
730 self.tr("Server Error"), match.group(3).split(":", 1)[1])
731 return True
732
733 return False
734
735 def __handleNumericMessage(self, match):
736 """
737 Private method to handle a server message containing a numeric code.
738
739 @param match reference to the match object
740 @return flag indicating, if the message was handled (boolean)
741 """
742 code = int(match.group(2))
743 if code < 400:
744 return self.__handleServerReply(
745 code, match.group(1), match.group(3))
746 else:
747 return self.__handleServerError(
748 code, match.group(1), match.group(3))
749
750 def __handleServerError(self, code, server, message):
751 """
752 Private slot to handle a server error reply.
753
754 @param code numerical code sent by the server (integer)
755 @param server name of the server (string)
756 @param message message sent by the server (string)
757 @return flag indicating, if the message was handled (boolean)
758 """
759 if code == 433:
760 if self.__registering:
761 self.__handleNickInUseLogin()
762 else:
763 self.__handleNickInUse()
764 else:
765 self.networkWidget.addServerMessage(self.tr("Error"), message)
766
767 return True
768
769 def __handleServerReply(self, code, server, message):
770 """
771 Private slot to handle a server reply.
772
773 @param code numerical code sent by the server (integer)
774 @param server name of the server (string)
775 @param message message sent by the server (string)
776 @return flag indicating, if the message was handled (boolean)
777 """
778 # determine message type
779 if code in [1, 2, 3, 4]:
780 msgType = self.tr("Welcome")
781 elif code == 5:
782 msgType = self.tr("Support")
783 elif code in [250, 251, 252, 253, 254, 255, 265, 266]:
784 msgType = self.tr("User")
785 elif code in [372, 375, 376]:
786 msgType = self.tr("MOTD")
787 elif code in [305, 306]:
788 msgType = self.tr("Away")
789 else:
790 msgType = self.tr("Info ({0})").format(code)
791
792 # special treatment for some messages
793 if code == 375:
794 message = self.tr("Message of the day")
795 elif code == 376:
796 message = self.tr("End of message of the day")
797 elif code == 4:
798 parts = message.strip().split()
799 message = self.tr(
800 "Server {0} (Version {1}), User-Modes: {2},"
801 " Channel-Modes: {3}"
802 ).format(parts[1], parts[2], parts[3], parts[4])
803 elif code == 265:
804 parts = message.strip().split()
805 message = self.tr(
806 "Current users on {0}: {1}, max. {2}").format(
807 server, parts[1], parts[2])
808 elif code == 266:
809 parts = message.strip().split()
810 message = self.tr(
811 "Current users on the network: {0}, max. {1}").format(
812 parts[1], parts[2])
813 elif code == 305:
814 message = self.tr("You are no longer marked as being away.")
815 elif code == 306:
816 message = self.tr("You have been marked as being away.")
817 else:
818 first, message = message.split(None, 1)
819 if message.startswith(":"):
820 message = message[1:]
821 else:
822 message = message.replace(":", "", 1)
823
824 self.networkWidget.addServerMessage(msgType, message)
825
826 if code == 1:
827 # register with services after the welcome message
828 self.__connectionState = IrcWidget.ServerConnected
829 self.__registerWithServices()
830 self.networkWidget.setRegistered(True)
831 QTimer.singleShot(1000, self.__autoJoinChannels)
832 elif code == 5:
833 # extract the user privilege prefixes
834 # ... PREFIX=(ov)@+ ...
835 m = self.__prefixRe.match(message)
836 if m:
837 self.__setUserPrivilegePrefix(m.group(1), m.group(2))
838 # extract the channel type prefixes
839 # ... CHANTYPES=# ...
840 m = self.__chanTypesRe.match(message)
841 if m:
842 self.__setChannelTypePrefixes(m.group(1))
843
844 return True
845
846 def __registerWithServices(self):
847 """
848 Private method to register to services.
849 """
850 identity = self.__ircNetworkManager.getIdentity(self.__identityName)
851 if identity:
852 service = identity.getServiceName()
853 password = identity.getPassword()
854 if service and password:
855 self.__send("PRIVMSG " + service + " :identify " + password)
856
857 def __autoJoinChannels(self):
858 """
859 Private slot to join channels automatically once a server got
860 connected.
861 """
862 for channel in self.networkWidget.getNetworkChannels():
863 if channel.autoJoin():
864 name = channel.getName()
865 key = channel.getKey()
866 self.joinChannel(name, key)
867
868 def __tcpError(self, error):
869 """
870 Private slot to handle errors reported by the TCP socket.
871
872 @param error error code reported by the socket
873 (QAbstractSocket.SocketError)
874 """
875 if error == QAbstractSocket.SocketError.RemoteHostClosedError:
876 # ignore this one, it's a disconnect
877 if self.__sslErrorLock:
878 self.networkWidget.addErrorMessage(
879 self.tr("SSL Error"),
880 self.tr(
881 """Connection to server {0} (port {1}) lost while"""
882 """ waiting for user response to an SSL error.""")
883 .format(self.__server.getName(), self.__server.getPort()))
884 self.__connectionState = IrcWidget.ServerDisconnected
885 elif error == QAbstractSocket.SocketError.HostNotFoundError:
886 self.networkWidget.addErrorMessage(
887 self.tr("Socket Error"),
888 self.tr(
889 "The host was not found. Please check the host name"
890 " and port settings."))
891 elif error == QAbstractSocket.SocketError.ConnectionRefusedError:
892 self.networkWidget.addErrorMessage(
893 self.tr("Socket Error"),
894 self.tr(
895 "The connection was refused by the peer. Please check the"
896 " host name and port settings."))
897 elif error == QAbstractSocket.SocketError.SslHandshakeFailedError:
898 self.networkWidget.addErrorMessage(
899 self.tr("Socket Error"),
900 self.tr("The SSL handshake failed."))
901 else:
902 if self.__socket:
903 self.networkWidget.addErrorMessage(
904 self.tr("Socket Error"),
905 self.tr(
906 "The following network error occurred:<br/>{0}")
907 .format(self.__socket.errorString()))
908 else:
909 self.networkWidget.addErrorMessage(
910 self.tr("Socket Error"),
911 self.tr("A network error occurred."))
912
913 def __sslErrors(self, errors):
914 """
915 Private slot to handle SSL errors.
916
917 @param errors list of SSL errors (list of QSslError)
918 """
919 ignored, defaultChanged = self.__sslErrorHandler.sslErrors(
920 errors, self.__server.getName(), self.__server.getPort())
921 if ignored == E5SslErrorState.NOT_IGNORED:
922 self.networkWidget.addErrorMessage(
923 self.tr("SSL Error"),
924 self.tr(
925 """Could not connect to {0} (port {1}) using an SSL"""
926 """ encrypted connection. Either the server does not"""
927 """ support SSL (did you use the correct port?) or"""
928 """ you rejected the certificate.""")
929 .format(self.__server.getName(), self.__server.getPort()))
930 self.__socket.close()
931 else:
932 if defaultChanged:
933 self.__socket.setSslConfiguration(
934 QSslConfiguration.defaultConfiguration())
935 if ignored == E5SslErrorState.USER_IGNORED:
936 self.networkWidget.addErrorMessage(
937 self.tr("SSL Error"),
938 self.tr(
939 """The SSL certificate for the server {0} (port {1})"""
940 """ failed the authenticity check. SSL errors"""
941 """ were accepted by you.""")
942 .format(self.__server.getName(), self.__server.getPort()))
943 if self.__connectionState == IrcWidget.ServerConnecting:
944 self.__socket.ignoreSslErrors()
945
946 def __setUserPrivilegePrefix(self, prefix1, prefix2):
947 """
948 Private method to set the user privilege prefix.
949
950 @param prefix1 first part of the prefix (string)
951 @param prefix2 indictors the first part gets mapped to (string)
952 """
953 # PREFIX=(ov)@+
954 # o = @ -> @ircbot , channel operator
955 # v = + -> +userName , voice operator
956 for i in range(len(prefix1)):
957 self.__userPrefix["+" + prefix1[i]] = prefix2[i]
958 self.__userPrefix["-" + prefix1[i]] = ""
959
960 def __ping(self, match):
961 """
962 Private method to handle a PING message.
963
964 @param match reference to the match object
965 @return flag indicating, if the message was handled (boolean)
966 """
967 self.__send("PONG " + match.group(1))
968 return True
969
970 def __handleCtcp(self, match):
971 """
972 Private method to handle a CTCP command.
973
974 @param match reference to the match object
975 @return flag indicating, if the message was handled (boolean)
976 """
977 # group(1) sender user name
978 # group(2) sender user@host
979 # group(3) target nick
980 # group(4) message
981 if match.group(4).startswith("\x01"):
982 ctcpCommand = match.group(4)[1:].split("\x01", 1)[0]
983 if " " in ctcpCommand:
984 ctcpRequest, ctcpArg = ctcpCommand.split(" ", 1)
985 else:
986 ctcpRequest, ctcpArg = ctcpCommand, ""
987 ctcpRequest = ctcpRequest.lower()
988 if ctcpRequest == "version":
989 if Version.startswith("@@"):
990 vers = ""
991 else:
992 vers = " " + Version
993 msg = "Eric IRC client{0}, {1}".format(vers, Copyright)
994 self.networkWidget.addServerMessage(
995 self.tr("CTCP"),
996 self.tr("Received Version request from {0}.").format(
997 match.group(1)))
998 self.__sendCtcpReply(match.group(1), "VERSION " + msg)
999 elif ctcpRequest == "ping":
1000 self.networkWidget.addServerMessage(
1001 self.tr("CTCP"),
1002 self.tr(
1003 "Received CTCP-PING request from {0},"
1004 " sending answer.").format(match.group(1)))
1005 self.__sendCtcpReply(
1006 match.group(1), "PING {0}".format(ctcpArg))
1007 elif ctcpRequest == "clientinfo":
1008 self.networkWidget.addServerMessage(
1009 self.tr("CTCP"),
1010 self.tr(
1011 "Received CTCP-CLIENTINFO request from {0},"
1012 " sending answer.").format(match.group(1)))
1013 self.__sendCtcpReply(
1014 match.group(1),
1015 "CLIENTINFO CLIENTINFO PING VERSION")
1016 else:
1017 self.networkWidget.addServerMessage(
1018 self.tr("CTCP"),
1019 self.tr(
1020 "Received unknown CTCP-{0} request from {1}.")
1021 .format(ctcpRequest, match.group(1)))
1022 return True
1023
1024 return False
1025
1026 def __updateUsersCount(self):
1027 """
1028 Private method to update the users count on the channel tabs.
1029 """
1030 for channel in self.__channelList:
1031 index = self.channelsWidget.indexOf(channel)
1032 self.channelsWidget.setTabText(
1033 index,
1034 self.tr("{0} ({1})", "channel name, users count").format(
1035 channel.name(), channel.getUsersCount()))
1036
1037 def __handleNickInUseLogin(self):
1038 """
1039 Private method to handle a 443 server error at login.
1040 """
1041 self.__nickIndex += 1
1042 try:
1043 identity = self.__ircNetworkManager.getIdentity(
1044 self.__identityName)
1045 if identity:
1046 nick = identity.getNickNames()[self.__nickIndex]
1047 self.__nickName = nick
1048 else:
1049 self.__connectNetwork("", False, True)
1050 self.__nickName = ""
1051 self.__nickIndex = -1
1052 return
1053 except IndexError:
1054 self.networkWidget.addServerMessage(
1055 self.tr("Critical"),
1056 self.tr(
1057 "No nickname acceptable to the server configured"
1058 " for <b>{0}</b>. Disconnecting...")
1059 .format(self.__userName),
1060 filterMsg=False)
1061 self.__connectNetwork("", False, True)
1062 self.__nickName = ""
1063 self.__nickIndex = -1
1064 return
1065
1066 self.networkWidget.setNickName(nick)
1067 self.__send("NICK " + nick)
1068
1069 def __handleNickInUse(self):
1070 """
1071 Private method to handle a 443 server error.
1072 """
1073 self.networkWidget.addServerMessage(
1074 self.tr("Critical"),
1075 self.tr("The given nickname is already in use."))
1076
1077 def __changeNick(self, nick):
1078 """
1079 Private slot to use a new nick name.
1080
1081 @param nick nick name to use (str)
1082 """
1083 if nick and nick != self.__nickName:
1084 self.__send("NICK " + nick)
1085
1086 def __setChannelTypePrefixes(self, prefixes):
1087 """
1088 Private method to set the channel type prefixes.
1089
1090 @param prefixes channel prefix characters (string)
1091 """
1092 self.__channelTypePrefixes = prefixes
1093
1094 def isChannelName(self, name):
1095 """
1096 Public method to check, if the given name is a channel name.
1097
1098 @param name name to check (string)
1099 @return flag indicating a channel name (boolean)
1100 """
1101 if not name:
1102 return False
1103
1104 if self.__channelTypePrefixes:
1105 return name[0] in self.__channelTypePrefixes
1106 else:
1107 return name[0] in "#&"
1108
1109 def __away(self, isAway):
1110 """
1111 Private slot handling the change of the away state.
1112
1113 @param isAway flag indicating the current away state (boolean)
1114 """
1115 if isAway and self.__identityName:
1116 identity = self.__ircNetworkManager.getIdentity(
1117 self.__identityName)
1118 if identity and identity.rememberAwayPosition():
1119 for channel in self.__channelList:
1120 channel.setMarkerLine()

eric ide

mercurial