ExtensionIrc/IrcWidget.py

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

eric ide

mercurial