|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the IRC channel widget. |
|
8 """ |
|
9 |
|
10 import pathlib |
|
11 import re |
|
12 from itertools import zip_longest |
|
13 |
|
14 from PyQt6.QtCore import ( |
|
15 QCoreApplication, |
|
16 QDateTime, |
|
17 QPoint, |
|
18 QTimer, |
|
19 QUrl, |
|
20 pyqtSignal, |
|
21 pyqtSlot, |
|
22 ) |
|
23 from PyQt6.QtGui import QDesktopServices, QIcon, QPainter, QTextCursor |
|
24 from PyQt6.QtWidgets import ( |
|
25 QApplication, |
|
26 QInputDialog, |
|
27 QLineEdit, |
|
28 QListWidgetItem, |
|
29 QMenu, |
|
30 QWidget, |
|
31 ) |
|
32 |
|
33 from eric7 import EricUtilities |
|
34 from eric7.__version__ import Version |
|
35 from eric7.EricGui import EricPixmapCache |
|
36 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
|
37 from eric7.EricWidgets.EricApplication import ericApp |
|
38 from eric7.SystemUtilities import OSUtilities |
|
39 from eric7.UI.Info import Copyright |
|
40 from PluginExtensionIrc import ircExtensionPluginObject |
|
41 |
|
42 from .IrcUtilities import getChannelModesDict, ircFilter, ircTimestamp |
|
43 from .Ui_IrcChannelWidget import Ui_IrcChannelWidget |
|
44 |
|
45 |
|
46 class IrcUserItem(QListWidgetItem): |
|
47 """ |
|
48 Class implementing a list widget item containing an IRC channel user. |
|
49 """ |
|
50 |
|
51 Normal = 0x00 # no privileges |
|
52 Operator = 0x01 # channel operator |
|
53 Voice = 0x02 # voice operator |
|
54 Admin = 0x04 # administrator |
|
55 Halfop = 0x08 # half operator |
|
56 Owner = 0x10 # channel owner |
|
57 Away = 0x80 # user away |
|
58 |
|
59 PrivilegeMapping = { |
|
60 "a": Away, |
|
61 "o": Operator, |
|
62 "O": Owner, |
|
63 "v": Voice, |
|
64 } |
|
65 |
|
66 def __init__(self, name, parent=None): |
|
67 """ |
|
68 Constructor |
|
69 |
|
70 @param name string with user name and privilege prefix |
|
71 @type str |
|
72 @param parent reference to the parent widget |
|
73 @type QListWidget or QListWidgetItem |
|
74 """ |
|
75 super().__init__(name, parent) |
|
76 |
|
77 self.__privilege = IrcUserItem.Normal |
|
78 self.__name = name |
|
79 self.__ignored = False |
|
80 |
|
81 self.__setText() |
|
82 self.__setIcon() |
|
83 |
|
84 def name(self): |
|
85 """ |
|
86 Public method to get the user name. |
|
87 |
|
88 @return user name |
|
89 @rtype str |
|
90 """ |
|
91 return self.__name |
|
92 |
|
93 def setName(self, name): |
|
94 """ |
|
95 Public method to set a new nick name. |
|
96 |
|
97 @param name new nick name for the user |
|
98 @type str |
|
99 """ |
|
100 self.__name = name |
|
101 self.__setText() |
|
102 |
|
103 def changePrivilege(self, privilege): |
|
104 """ |
|
105 Public method to set or unset a user privilege. |
|
106 |
|
107 @param privilege privilege to set or unset |
|
108 @type str |
|
109 """ |
|
110 oper = privilege[0] |
|
111 priv = privilege[1] |
|
112 if priv in IrcUserItem.PrivilegeMapping: |
|
113 if oper == "+": |
|
114 self.__privilege |= IrcUserItem.PrivilegeMapping[priv] |
|
115 elif oper == "-": |
|
116 self.__privilege &= ~IrcUserItem.PrivilegeMapping[priv] |
|
117 self.__setIcon() |
|
118 |
|
119 def clearPrivileges(self): |
|
120 """ |
|
121 Public method to clear the user privileges. |
|
122 """ |
|
123 self.__privilege = IrcUserItem.Normal |
|
124 self.__setIcon() |
|
125 |
|
126 def __setText(self): |
|
127 """ |
|
128 Private method to set the user item text. |
|
129 """ |
|
130 if self.__ignored: |
|
131 self.setText( |
|
132 QCoreApplication.translate("IrcUserItem", "{0} (ignored)").format( |
|
133 self.__name |
|
134 ) |
|
135 ) |
|
136 else: |
|
137 self.setText(self.__name) |
|
138 |
|
139 def __setIcon(self): |
|
140 """ |
|
141 Private method to set the icon dependent on user privileges. |
|
142 """ |
|
143 # step 1: determine the icon |
|
144 if self.__privilege & IrcUserItem.Voice: |
|
145 icon = ircExtensionPluginObject.getIcon("ircVoice") |
|
146 elif self.__privilege & IrcUserItem.Owner: |
|
147 icon = ircExtensionPluginObject.getIcon("ircOwner") |
|
148 elif self.__privilege & IrcUserItem.Operator: |
|
149 icon = ircExtensionPluginObject.getIcon("ircOp") |
|
150 elif self.__privilege & IrcUserItem.Halfop: |
|
151 icon = ircExtensionPluginObject.getIcon("ircHalfop") |
|
152 elif self.__privilege & IrcUserItem.Admin: |
|
153 icon = ircExtensionPluginObject.getIcon("ircAdmin") |
|
154 else: |
|
155 icon = ircExtensionPluginObject.getIcon("ircNormal") |
|
156 if self.__privilege & IrcUserItem.Away: |
|
157 icon = self.__awayIcon(icon) |
|
158 |
|
159 # step 2: set the icon |
|
160 self.setIcon(icon) |
|
161 |
|
162 def __awayIcon(self, icon): |
|
163 """ |
|
164 Private method to convert an icon to an away icon. |
|
165 |
|
166 @param icon icon to be converted |
|
167 @type QIcon |
|
168 @return away icon |
|
169 @rtype QIcon |
|
170 """ |
|
171 pix1 = icon.pixmap(16, 16) |
|
172 pix2 = ircExtensionPluginObject.getPixmap("ircAway") |
|
173 painter = QPainter(pix1) |
|
174 painter.drawPixmap(0, 0, pix2) |
|
175 painter.end() |
|
176 return QIcon(pix1) |
|
177 |
|
178 def parseWhoFlags(self, flags): |
|
179 """ |
|
180 Public method to parse the user flags reported by a WHO command. |
|
181 |
|
182 @param flags user flags as reported by WHO |
|
183 @type str |
|
184 """ |
|
185 # H The user is not away. |
|
186 # G The user is set away. |
|
187 # * The user is an IRC operator. |
|
188 # @ The user is a channel op in the channel listed in the first field. |
|
189 # + The user is voiced in the channel listed. |
|
190 if flags.endswith("@"): |
|
191 privilege = IrcUserItem.Operator |
|
192 elif flags.endswith("+"): |
|
193 privilege = IrcUserItem.Voice |
|
194 else: |
|
195 privilege = IrcUserItem.Normal |
|
196 if "*" in flags: |
|
197 privilege = IrcUserItem.Admin |
|
198 if flags.startswith("G"): |
|
199 privilege |= IrcUserItem.Away |
|
200 self.__privilege = privilege |
|
201 self.__setIcon() |
|
202 |
|
203 def canChangeTopic(self): |
|
204 """ |
|
205 Public method to check, if the user is allowed to change the topic. |
|
206 |
|
207 @return flag indicating that the topic can be changed |
|
208 @rtype bool |
|
209 """ |
|
210 return ( |
|
211 bool(self.__privilege & IrcUserItem.Operator) |
|
212 or bool(self.__privilege & IrcUserItem.Admin) |
|
213 or bool(self.__privilege & IrcUserItem.Owner) |
|
214 ) |
|
215 |
|
216 def setIgnored(self, ignored): |
|
217 """ |
|
218 Public method to set the user status to ignored. |
|
219 |
|
220 @param ignored flag indicating the new ignored status |
|
221 @type bool |
|
222 """ |
|
223 self.__ignored = ignored |
|
224 self.__setText() |
|
225 |
|
226 def isIgnored(self): |
|
227 """ |
|
228 Public method to check, if this user is ignored. |
|
229 |
|
230 @return flag indicating the ignored status |
|
231 @rtype bool |
|
232 """ |
|
233 return self.__ignored |
|
234 |
|
235 |
|
236 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget): |
|
237 """ |
|
238 Class implementing the IRC channel widget. |
|
239 |
|
240 @signal sendData(str) emitted to send a message to the channel |
|
241 @signal sendCtcpRequest(str, str, str) emitted to send a CTCP request |
|
242 @signal sendCtcpReply(str, str) emitted to send a CTCP reply |
|
243 @signal channelClosed(str) emitted after the user has left the channel |
|
244 @signal openPrivateChat(str) emitted to open a "channel" for private |
|
245 messages |
|
246 @signal awayCommand(str) emitted to set the away status via the /away |
|
247 command |
|
248 @signal leaveChannels(list) emitted to leave a list of channels |
|
249 @signal leaveAllChannels() emitted to leave all channels |
|
250 """ |
|
251 |
|
252 sendData = pyqtSignal(str) |
|
253 sendCtcpRequest = pyqtSignal(str, str, str) |
|
254 sendCtcpReply = pyqtSignal(str, str) |
|
255 channelClosed = pyqtSignal(str) |
|
256 openPrivateChat = pyqtSignal(str) |
|
257 awayCommand = pyqtSignal(str) |
|
258 leaveChannels = pyqtSignal(list) |
|
259 leaveAllChannels = pyqtSignal() |
|
260 |
|
261 UrlRe = re.compile( |
|
262 r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+""" |
|
263 r"""(?:[\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)""" |
|
264 ) |
|
265 |
|
266 JoinIndicator = "-->" |
|
267 LeaveIndicator = "<--" |
|
268 MessageIndicator = "***" |
|
269 |
|
270 def __init__(self, parent=None): |
|
271 """ |
|
272 Constructor |
|
273 |
|
274 @param parent reference to the parent widget |
|
275 @type QWidget |
|
276 """ |
|
277 super().__init__(parent) |
|
278 self.setupUi(self) |
|
279 |
|
280 self.__ui = ericApp().getObject("UserInterface") |
|
281 self.__ircWidget = parent |
|
282 |
|
283 self.editTopicButton.setIcon(ircExtensionPluginObject.getIcon("ircEditTopic")) |
|
284 self.editTopicButton.hide() |
|
285 |
|
286 height = self.usersList.height() + self.messages.height() |
|
287 self.splitter.setSizes([int(height * 0.3), int(height * 0.7)]) |
|
288 |
|
289 self.__initMessagesMenu() |
|
290 self.__initUsersMenu() |
|
291 |
|
292 self.__name = "" |
|
293 self.__userName = "" |
|
294 self.__partMessage = "" |
|
295 self.__prefixToPrivilege = {} |
|
296 self.__private = False |
|
297 self.__privatePartner = "" |
|
298 self.__whoIsNick = "" |
|
299 |
|
300 self.__markerLine = "" |
|
301 self.__hidden = True |
|
302 |
|
303 self.__serviceNamesLower = ["nickserv", "chanserv", "memoserv"] |
|
304 |
|
305 self.__patterns = [ |
|
306 # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message |
|
307 # :foo_!n=foo@foohost.bar.net PRIVMSG bar_ :some long message |
|
308 (re.compile(r":([^!]+)!([^ ]+)\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message), |
|
309 # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide |
|
310 (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin), |
|
311 # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message |
|
312 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart), |
|
313 # :foo_!n=foo@foohost.bar.net PART #eric-ide |
|
314 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart), |
|
315 # :foo_!n=foo@foohost.bar.net QUIT :quit message |
|
316 (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit), |
|
317 # :foo_!n=foo@foohost.bar.net QUIT |
|
318 (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit), |
|
319 # :foo_!n=foo@foohost.bar.net NICK :newnick |
|
320 (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange), |
|
321 # :foo_!n=foo@foohost.bar.net MODE #eric-ide +o foo_ |
|
322 ( |
|
323 re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([+-][ovO]+)\s([^ ]+).*"), |
|
324 self.__setUserPrivilege, |
|
325 ), |
|
326 # :cameron.libera.chat MODE #eric-ide +ns |
|
327 (re.compile(r":([^ ]+)\sMODE\s([^ ]+)\s(.+)"), self.__updateChannelModes), |
|
328 # :foo_!n=foo@foohost.bar.net TOPIC #eric-ide :eric - Python IDE |
|
329 (re.compile(r":.*\sTOPIC\s([^ ]+)\s:(.*)"), self.__setTopic), |
|
330 # :sturgeon.libera.chat 301 foo_ bar :Gone away for now |
|
331 (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"), self.__userAway), |
|
332 # :sturgeon.libera.chat 315 foo_ #eric-ide :End of /WHO list. |
|
333 (re.compile(r":.*\s315\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoEnd), |
|
334 # :zelazny.libera.chat 324 foo_ #eric-ide +cnt |
|
335 (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes), |
|
336 # :zelazny.libera.chat 328 foo_ #eric-ide :http://www.bugger.com/ |
|
337 (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl), |
|
338 # :zelazny.libera.chat 329 foo_ #eric-ide 1353001005 |
|
339 (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"), self.__channelCreated), |
|
340 # :zelazny.libera.chat 332 foo_ #eric-ide :eric support channel |
|
341 (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic), |
|
342 # :zelazny.libera.chat foo_ 333 #eric-ide foo 1353089020 |
|
343 (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated), |
|
344 # :cameron.libera.chat 352 detlev_ #eric-ide ~foo foohost.bar.net |
|
345 # cameron.libera.chat foo_ H :0 Foo Bar |
|
346 ( |
|
347 re.compile( |
|
348 r":.*\s352\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s[^ ]+\s([^ ]+)" |
|
349 r"\s([^ ]+)\s:\d+\s(.*)" |
|
350 ), |
|
351 self.__whoEntry, |
|
352 ), |
|
353 # :zelazny.libera.chat 353 foo_ @ #eric-ide :@user1 +user2 user3 |
|
354 (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList), |
|
355 # :sturgeon.libera.chat 354 foo_ 42 ChanServ H@ |
|
356 (re.compile(r":.*\s354\s[^ ]+\s42\s([^ ]+)\s(.*)"), self.__autoWhoEntry), |
|
357 # :zelazny.libera.chat 366 foo_ #eric-ide :End of /NAMES list. |
|
358 (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore), |
|
359 # :sturgeon.libera.chat 704 foo_ index :Help topics available: |
|
360 (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help), |
|
361 # WHOIS replies |
|
362 # :sturgeon.libera.chat 311 foo_ bar ~bar barhost.foo.net * :Bar |
|
363 ( |
|
364 re.compile(r":.*\s311\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s\*\s:(.*)"), |
|
365 self.__whoIsUser, |
|
366 ), |
|
367 # :sturgeon.libera.chat 319 foo_ bar :@#eric-ide |
|
368 (re.compile(r":.*\s319\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsChannels), |
|
369 # :sturgeon.libera.chat 312 foo_ bar sturgeon.libera.chat :London |
|
370 ( |
|
371 re.compile(r":.*\s312\s[^ ]+\s([^ ]+)\s([^ ]+)\s:(.*)"), |
|
372 self.__whoIsServer, |
|
373 ), |
|
374 # :sturgeon.libera.chat 671 foo_ bar :is using a secure connection |
|
375 (re.compile(r":.*\s671\s[^ ]+\s([^ ]+)\s:.*"), self.__whoIsSecure), |
|
376 # :sturgeon.libera.chat 317 foo_ bar 3758 1355046912 :seconds |
|
377 # idle, signon time |
|
378 ( |
|
379 re.compile(r":.*\s317\s[^ ]+\s([^ ]+)\s(\d+)\s(\d+)\s:.*"), |
|
380 self.__whoIsIdle, |
|
381 ), |
|
382 # :sturgeon.libera.chat 330 foo_ bar bar :is logged in as |
|
383 ( |
|
384 re.compile(r":.*\s330\s[^ ]+\s([^ ]+)\s([^ ]+)\s:.*"), |
|
385 self.__whoIsAccount, |
|
386 ), |
|
387 # :sturgeon.libera.chat 318 foo_ bar :End of /WHOIS list. |
|
388 (re.compile(r":.*\s318\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsEnd), |
|
389 # :sturgeon.libera.chat 307 foo_ bar :is an identified user |
|
390 (re.compile(r":.*\s307\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsIdentify), |
|
391 # :sturgeon.libera.chat 320 foo_ bar :is an identified user |
|
392 (re.compile(r":.*\s320\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsIdentify), |
|
393 # :sturgeon.libera.chat 310 foo_ bar :is available for help |
|
394 (re.compile(r":.*\s310\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsHelper), |
|
395 # :sturgeon.libera.chat 338 foo_ bar real.ident@real.host |
|
396 # 12.34.56.78 :Actual user@host, Actual IP |
|
397 ( |
|
398 re.compile(r":.*\s338\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s:.*"), |
|
399 self.__whoIsActually, |
|
400 ), |
|
401 # :sturgeon.libera.chat 313 foo_ bar :is an IRC Operator |
|
402 (re.compile(r":.*\s313\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsOperator), |
|
403 # :sturgeon.libera.chat 378 foo_ bar :is connecting from |
|
404 # *@mnch-4d044d5a.pool.mediaWays.net 77.4.77.90 |
|
405 ( |
|
406 re.compile(r":.*\s378\s[^ ]+\s([^ ]+)\s:.*\s([^ ]+)\s([^ ]+)"), |
|
407 self.__whoIsConnection, |
|
408 ), |
|
409 ] |
|
410 |
|
411 self.__autoWhoTemplate = "WHO {0} %tnf,42" |
|
412 self.__autoWhoTimer = QTimer() |
|
413 self.__autoWhoTimer.setSingleShot(True) |
|
414 self.__autoWhoTimer.timeout.connect(self.__sendAutoWhoCommand) |
|
415 self.__autoWhoRequested = False |
|
416 |
|
417 @pyqtSlot() |
|
418 def on_messageEdit_returnPressed(self): |
|
419 """ |
|
420 Private slot to send a message to the channel. |
|
421 """ |
|
422 msg = self.messageEdit.text() |
|
423 if msg: |
|
424 self.__processUserMessage(msg) |
|
425 |
|
426 def __processUserMessage(self, msg): |
|
427 """ |
|
428 Private method to process a message entered by the user or via the |
|
429 user list context menu. |
|
430 |
|
431 @param msg message to be processed |
|
432 @type str |
|
433 """ |
|
434 self.messages.append( |
|
435 '<font color="{0}">{2} <b><</b><font color="{1}">{3}</font>' |
|
436 "<b>></b> {4}</font>".format( |
|
437 ircExtensionPluginObject.getPreferences("ChannelMessageColour"), |
|
438 ircExtensionPluginObject.getPreferences("OwnNickColour"), |
|
439 ircTimestamp(), |
|
440 self.__userName, |
|
441 EricUtilities.html_encode(msg), |
|
442 ) |
|
443 ) |
|
444 |
|
445 if msg.startswith("/"): |
|
446 if self.__private: |
|
447 EricMessageBox.information( |
|
448 self, |
|
449 self.tr("Send Message"), |
|
450 self.tr( |
|
451 """Messages starting with a '/' are not allowed""" |
|
452 """ in private chats.""" |
|
453 ), |
|
454 ) |
|
455 else: |
|
456 sendData = True |
|
457 # flag set to False, if command was handled |
|
458 |
|
459 msgList = msg.split() |
|
460 cmd = msgList[0][1:].upper() |
|
461 if cmd in ["MSG", "QUERY"]: |
|
462 cmd = "PRIVMSG" |
|
463 if len(msgList) > 1: |
|
464 if msgList[1].strip().lower() in self.__serviceNamesLower: |
|
465 msg = ( |
|
466 "PRIVMSG " |
|
467 + msgList[1].strip().lower() |
|
468 + " :" |
|
469 + " ".join(msgList[2:]) |
|
470 ) |
|
471 else: |
|
472 msg = "PRIVMSG {0} :{1}".format( |
|
473 msgList[1], " ".join(msgList[2:]) |
|
474 ) |
|
475 else: |
|
476 msgList[0] = cmd |
|
477 msg = " ".join(msgList) |
|
478 elif cmd == "NOTICE": |
|
479 if len(msgList) > 2: |
|
480 msg = "NOTICE {0} :{1}".format( |
|
481 msgList[1], " ".join(msgList[2:]) |
|
482 ) |
|
483 else: |
|
484 msg = "NOTICE {0}".format(" ".join(msgList[1:])) |
|
485 elif cmd == "PING": |
|
486 receiver = msgList[1] |
|
487 msg = "PING {0} " |
|
488 self.sendCtcpRequest.emit(receiver, "PING", "") |
|
489 sendData = False |
|
490 elif cmd == "IGNORE": |
|
491 sendData = False |
|
492 if len(msgList) > 1: |
|
493 if msgList[1] == "-r": |
|
494 ignored = False |
|
495 userNamesList = msgList[2:] |
|
496 else: |
|
497 ignored = True |
|
498 userNamesList = msgList[1:] |
|
499 else: |
|
500 userNamesList = [] |
|
501 userNames = ",".join(u.rstrip(",") for u in userNamesList).split( |
|
502 "," |
|
503 ) |
|
504 for userName in userNames: |
|
505 itm = self.__findUser(userName) |
|
506 if itm: |
|
507 itm.setIgnored(ignored) |
|
508 elif cmd == "UNIGNORE": |
|
509 sendData = False |
|
510 if len(msgList) > 1: |
|
511 userNamesList = msgList[1:] |
|
512 else: |
|
513 userNamesList = [] |
|
514 userNames = ",".join(u.rstrip(",") for u in userNamesList).split( |
|
515 "," |
|
516 ) |
|
517 for userName in userNames: |
|
518 itm = self.__findUser(userName) |
|
519 if itm: |
|
520 itm.setIgnored(False) |
|
521 elif cmd == "AWAY": |
|
522 sendData = False |
|
523 if len(msgList) > 1: |
|
524 msg = " ".join(msgList[1:]) |
|
525 else: |
|
526 msg = "" |
|
527 self.awayCommand.emit(msg) |
|
528 elif cmd == "JOIN": |
|
529 sendData = False |
|
530 if len(msgList) > 1: |
|
531 channels = msgList[1].split(",") |
|
532 if len(msgList) > 2: |
|
533 keys = msgList[2].split(",") |
|
534 else: |
|
535 keys = [] |
|
536 for channel, key in zip_longest(channels, keys, fillvalue=""): |
|
537 self.__ircWidget.joinChannel(channel, key) |
|
538 elif cmd == "PART": |
|
539 sendData = False |
|
540 if len(msgList) == 1: |
|
541 self.leaveChannel() |
|
542 else: |
|
543 self.leaveChannels.emit(msgList[1:]) |
|
544 elif cmd == "PARTALL": |
|
545 sendData = False |
|
546 self.leaveAllChannels.emit() |
|
547 else: |
|
548 msg = msg[1:] |
|
549 if sendData: |
|
550 self.sendData.emit(msg) |
|
551 else: |
|
552 if self.__private: |
|
553 self.sendData.emit("PRIVMSG " + self.__privatePartner + " :" + msg) |
|
554 else: |
|
555 self.sendData.emit("PRIVMSG " + self.__name + " :" + msg) |
|
556 |
|
557 self.messageEdit.clear() |
|
558 self.unsetMarkerLine() |
|
559 |
|
560 def requestLeave(self): |
|
561 """ |
|
562 Public method to leave the channel. |
|
563 """ |
|
564 ok = EricMessageBox.yesNo( |
|
565 self, |
|
566 self.tr("Leave IRC channel"), |
|
567 self.tr( |
|
568 """Do you really want to leave the IRC channel <b>{0}</b>?""" |
|
569 ).format(self.__name), |
|
570 ) |
|
571 if ok: |
|
572 self.leaveChannel() |
|
573 |
|
574 def leaveChannel(self): |
|
575 """ |
|
576 Public slot to leave the channel. |
|
577 """ |
|
578 if not self.__private: |
|
579 self.sendData.emit("PART " + self.__name + " :" + self.__partMessage) |
|
580 self.channelClosed.emit(self.__name) |
|
581 |
|
582 def name(self): |
|
583 """ |
|
584 Public method to get the name of the channel. |
|
585 |
|
586 @return name of the channel |
|
587 @rtype str |
|
588 """ |
|
589 return self.__name |
|
590 |
|
591 def setName(self, name): |
|
592 """ |
|
593 Public method to set the name of the channel. |
|
594 |
|
595 @param name of the channel |
|
596 @type str |
|
597 """ |
|
598 self.__name = name |
|
599 |
|
600 def getUsersCount(self): |
|
601 """ |
|
602 Public method to get the users count of the channel. |
|
603 |
|
604 @return users count of the channel |
|
605 @rtype int |
|
606 """ |
|
607 return self.usersList.count() |
|
608 |
|
609 def userName(self): |
|
610 """ |
|
611 Public method to get the nick name of the user. |
|
612 |
|
613 @return nick name of the user |
|
614 @rtype str |
|
615 """ |
|
616 return self.__userName |
|
617 |
|
618 def setUserName(self, name): |
|
619 """ |
|
620 Public method to set the user name for the channel. |
|
621 |
|
622 @param name user name for the channel |
|
623 @type str |
|
624 """ |
|
625 self.__userName = name |
|
626 |
|
627 def partMessage(self): |
|
628 """ |
|
629 Public method to get the part message. |
|
630 |
|
631 @return part message |
|
632 @rtype str |
|
633 """ |
|
634 return self.__partMessage |
|
635 |
|
636 def setPartMessage(self, message): |
|
637 """ |
|
638 Public method to set the part message. |
|
639 |
|
640 @param message message to be used for PART messages |
|
641 @type str |
|
642 """ |
|
643 self.__partMessage = message |
|
644 |
|
645 def setPrivate(self, private, partner=""): |
|
646 """ |
|
647 Public method to set the private chat mode. |
|
648 |
|
649 @param private flag indicating private chat mode |
|
650 @type bool |
|
651 @param partner name of the partner user |
|
652 @type str |
|
653 """ |
|
654 self.__private = private |
|
655 self.__privatePartner = partner |
|
656 self.editTopicButton.setEnabled(private) |
|
657 |
|
658 def setPrivateInfo(self, infoText): |
|
659 """ |
|
660 Public method to set some info text for private chat mode. |
|
661 |
|
662 @param infoText info text to be shown |
|
663 @type str |
|
664 """ |
|
665 if self.__private: |
|
666 self.topicLabel.setText(infoText) |
|
667 |
|
668 def handleMessage(self, line): |
|
669 """ |
|
670 Public method to handle the message sent by the server. |
|
671 |
|
672 @param line server message |
|
673 @type str |
|
674 @return flag indicating, if the message was handled |
|
675 @rtype bool |
|
676 """ |
|
677 for patternRe, patternFunc in self.__patterns: |
|
678 match = patternRe.match(line) |
|
679 if match is not None and patternFunc(match): |
|
680 return True |
|
681 |
|
682 return False |
|
683 |
|
684 def __message(self, match): |
|
685 """ |
|
686 Private method to handle messages to the channel. |
|
687 |
|
688 @param match match object that matched the pattern |
|
689 @type re.Match |
|
690 @return flag indicating whether the message was handled |
|
691 @rtype bool |
|
692 """ |
|
693 # group(1) sender user name |
|
694 # group(2) sender user@host |
|
695 # group(3) target nick |
|
696 # group(4) message |
|
697 if match.group(3).lower() == self.__name.lower(): |
|
698 senderName = match.group(1) |
|
699 itm = self.__findUser(senderName) |
|
700 if itm and itm.isIgnored(): |
|
701 # user should be ignored |
|
702 return True |
|
703 |
|
704 if match.group(4).startswith("\x01"): |
|
705 return self.__handleCtcp(match) |
|
706 |
|
707 self.addMessage(senderName, match.group(4)) |
|
708 if self.__private and not self.topicLabel.text(): |
|
709 self.setPrivateInfo("{0} - {1}".format(match.group(1), match.group(2))) |
|
710 return True |
|
711 |
|
712 return False |
|
713 |
|
714 def addMessage(self, sender, msg): |
|
715 """ |
|
716 Public method to add a message from external. |
|
717 |
|
718 @param sender nick name of the sender |
|
719 @type str |
|
720 @param msg message received from sender |
|
721 @type str |
|
722 """ |
|
723 self.__appendMessage( |
|
724 '<font color="{0}">{2} <b><</b><font color="{1}">{3}</font>' |
|
725 "<b>></b> {4}</font>".format( |
|
726 ircExtensionPluginObject.getPreferences("ChannelMessageColour"), |
|
727 ircExtensionPluginObject.getPreferences("NickColour"), |
|
728 ircTimestamp(), |
|
729 sender, |
|
730 ircFilter(msg), |
|
731 ) |
|
732 ) |
|
733 if ircExtensionPluginObject.getPreferences("ShowNotifications"): |
|
734 if ircExtensionPluginObject.getPreferences("NotifyMessage"): |
|
735 self.__ui.showNotification( |
|
736 ircExtensionPluginObject.getPixmap("irc48"), |
|
737 self.tr("Channel Message"), |
|
738 msg, |
|
739 ) |
|
740 elif ( |
|
741 ircExtensionPluginObject.getPreferences("NotifyNick") |
|
742 and self.__userName.lower() in msg.lower() |
|
743 ): |
|
744 self.__ui.showNotification( |
|
745 ircExtensionPluginObject.getPixmap("irc48"), |
|
746 self.tr("Nick mentioned"), |
|
747 msg, |
|
748 ) |
|
749 |
|
750 def addUsers(self, users): |
|
751 """ |
|
752 Public method to add users to the channel. |
|
753 |
|
754 @param users list of user names to add |
|
755 @type list of str |
|
756 """ |
|
757 for user in users: |
|
758 itm = self.__findUser(user) |
|
759 if itm is None: |
|
760 IrcUserItem(name=user, parent=self.usersList) |
|
761 |
|
762 def __userJoin(self, match): |
|
763 """ |
|
764 Private method to handle a user joining the channel. |
|
765 |
|
766 @param match match object that matched the pattern |
|
767 @type re.Match |
|
768 @return flag indicating whether the message was handled |
|
769 @rtype bool |
|
770 """ |
|
771 if match.group(3).lower() == self.__name.lower(): |
|
772 if self.__userName != match.group(1): |
|
773 IrcUserItem(name=match.group(1), parent=self.usersList) |
|
774 msg = self.tr("{0} has joined the channel {1} ({2}).").format( |
|
775 match.group(1), self.__name, match.group(2) |
|
776 ) |
|
777 self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg) |
|
778 else: |
|
779 msg = self.tr("You have joined the channel {0} ({1}).").format( |
|
780 self.__name, match.group(2) |
|
781 ) |
|
782 self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg) |
|
783 if ircExtensionPluginObject.getPreferences( |
|
784 "ShowNotifications" |
|
785 ) and ircExtensionPluginObject.getPreferences("NotifyJoinPart"): |
|
786 self.__ui.showNotification( |
|
787 ircExtensionPluginObject.getPixmap("irc48"), |
|
788 self.tr("Join Channel"), |
|
789 msg, |
|
790 ) |
|
791 return True |
|
792 |
|
793 return False |
|
794 |
|
795 def __userPart(self, match): |
|
796 """ |
|
797 Private method to handle a user leaving the channel. |
|
798 |
|
799 @param match match object that matched the pattern |
|
800 @type re.Match |
|
801 @return flag indicating whether the message was handled |
|
802 @rtype bool |
|
803 """ |
|
804 if match.group(2).lower() == self.__name.lower(): |
|
805 itm = self.__findUser(match.group(1)) |
|
806 self.usersList.takeItem(self.usersList.row(itm)) |
|
807 del itm |
|
808 if match.lastindex == 2: |
|
809 msg = self.tr("{0} has left {1}.").format(match.group(1), self.__name) |
|
810 nmsg = msg |
|
811 self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg) |
|
812 else: |
|
813 msg = self.tr("{0} has left {1}: {2}.").format( |
|
814 match.group(1), self.__name, ircFilter(match.group(3)) |
|
815 ) |
|
816 nmsg = self.tr("{0} has left {1}: {2}.").format( |
|
817 match.group(1), self.__name, match.group(3) |
|
818 ) |
|
819 self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg) |
|
820 if ircExtensionPluginObject.getPreferences( |
|
821 "ShowNotifications" |
|
822 ) and ircExtensionPluginObject.getPreferences("NotifyJoinPart"): |
|
823 self.__ui.showNotification( |
|
824 ircExtensionPluginObject.getPixmap("irc48"), |
|
825 self.tr("Leave Channel"), |
|
826 nmsg, |
|
827 ) |
|
828 return True |
|
829 |
|
830 return False |
|
831 |
|
832 def __userQuit(self, match): |
|
833 """ |
|
834 Private method to handle a user logging off the server. |
|
835 |
|
836 @param match match object that matched the pattern |
|
837 @type re.Match |
|
838 @return flag indicating whether the message was handled |
|
839 @rtype bool |
|
840 """ |
|
841 itm = self.__findUser(match.group(1)) |
|
842 if itm: |
|
843 self.usersList.takeItem(self.usersList.row(itm)) |
|
844 del itm |
|
845 if match.lastindex == 1: |
|
846 msg = self.tr("{0} has quit {1}.").format(match.group(1), self.__name) |
|
847 self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg) |
|
848 else: |
|
849 msg = self.tr("{0} has quit {1}: {2}.").format( |
|
850 match.group(1), self.__name, ircFilter(match.group(2)) |
|
851 ) |
|
852 self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg) |
|
853 if ircExtensionPluginObject.getPreferences( |
|
854 "ShowNotifications" |
|
855 ) and ircExtensionPluginObject.getPreferences("NotifyJoinPart"): |
|
856 self.__ui.showNotification( |
|
857 ircExtensionPluginObject.getPixmap("irc48"), self.tr("Quit"), msg |
|
858 ) |
|
859 |
|
860 # always return False for other channels and server to process |
|
861 return False |
|
862 |
|
863 def __userNickChange(self, match): |
|
864 """ |
|
865 Private method to handle a nickname change of a user. |
|
866 |
|
867 @param match match object that matched the pattern |
|
868 @type re.Match |
|
869 @return flag indicating whether the message was handled |
|
870 @rtype bool |
|
871 """ |
|
872 itm = self.__findUser(match.group(1)) |
|
873 if itm: |
|
874 itm.setName(match.group(2)) |
|
875 if match.group(1) == self.__userName: |
|
876 self.__addManagementMessage( |
|
877 IrcChannelWidget.MessageIndicator, |
|
878 self.tr("You are now known as {0}.").format(match.group(2)), |
|
879 ) |
|
880 self.__userName = match.group(2) |
|
881 else: |
|
882 self.__addManagementMessage( |
|
883 IrcChannelWidget.MessageIndicator, |
|
884 self.tr("User {0} is now known as {1}.").format( |
|
885 match.group(1), match.group(2) |
|
886 ), |
|
887 ) |
|
888 |
|
889 # always return False for other channels and server to process |
|
890 return False |
|
891 |
|
892 def __userList(self, match): |
|
893 """ |
|
894 Private method to handle the receipt of a list of users of the channel. |
|
895 |
|
896 @param match match object that matched the pattern |
|
897 @type re.Match |
|
898 @return flag indicating whether the message was handled |
|
899 @rtype bool |
|
900 """ |
|
901 if match.group(1).lower() == self.__name.lower(): |
|
902 users = match.group(2).split() |
|
903 for user in users: |
|
904 userPrivileges, userName = self.__extractPrivilege(user) |
|
905 itm = self.__findUser(userName) |
|
906 if itm is None: |
|
907 itm = IrcUserItem(name=userName, parent=self.usersList) |
|
908 for privilege in userPrivileges: |
|
909 itm.changePrivilege(privilege) |
|
910 |
|
911 self.__setEditTopicButton() |
|
912 return True |
|
913 |
|
914 return False |
|
915 |
|
916 def __userAway(self, match): |
|
917 """ |
|
918 Private method to handle a topic change of the channel. |
|
919 |
|
920 @param match match object that matched the pattern |
|
921 @type re.Match |
|
922 @return flag indicating whether the message was handled |
|
923 @rtype bool |
|
924 """ |
|
925 if match.group(1).lower() == self.__name.lower(): |
|
926 self.__addManagementMessage( |
|
927 self.tr("Away"), |
|
928 self.tr("{0} is away: {1}").format(match.group(2), match.group(3)), |
|
929 ) |
|
930 return True |
|
931 |
|
932 return False |
|
933 |
|
934 def __setTopic(self, match): |
|
935 """ |
|
936 Private method to handle a topic change of the channel. |
|
937 |
|
938 @param match match object that matched the pattern |
|
939 @type re.Match |
|
940 @return flag indicating whether the message was handled |
|
941 @rtype bool |
|
942 """ |
|
943 if match.group(1).lower() == self.__name.lower(): |
|
944 self.topicLabel.setText(match.group(2)) |
|
945 self.__addManagementMessage( |
|
946 IrcChannelWidget.MessageIndicator, |
|
947 ircFilter( |
|
948 self.tr('The channel topic is: "{0}".').format(match.group(2)) |
|
949 ), |
|
950 ) |
|
951 return True |
|
952 |
|
953 return False |
|
954 |
|
955 def __topicCreated(self, match): |
|
956 """ |
|
957 Private method to handle a topic created message. |
|
958 |
|
959 @param match match object that matched the pattern |
|
960 @type re.Match |
|
961 @return flag indicating whether the message was handled |
|
962 @rtype bool |
|
963 """ |
|
964 if match.group(1).lower() == self.__name.lower(): |
|
965 self.__addManagementMessage( |
|
966 IrcChannelWidget.MessageIndicator, |
|
967 self.tr("The topic was set by {0} on {1}.").format( |
|
968 match.group(2), |
|
969 QDateTime.fromSecsSinceEpoch(int(match.group(3))).toString( |
|
970 "yyyy-MM-dd hh:mm" |
|
971 ), |
|
972 ), |
|
973 ) |
|
974 return True |
|
975 |
|
976 return False |
|
977 |
|
978 def __channelUrl(self, match): |
|
979 """ |
|
980 Private method to handle a channel URL message. |
|
981 |
|
982 @param match match object that matched the pattern |
|
983 @type re.Match |
|
984 @return flag indicating whether the message was handled |
|
985 @rtype bool |
|
986 """ |
|
987 if match.group(1).lower() == self.__name.lower(): |
|
988 self.__addManagementMessage( |
|
989 IrcChannelWidget.MessageIndicator, |
|
990 ircFilter(self.tr("Channel URL: {0}").format(match.group(2))), |
|
991 ) |
|
992 return True |
|
993 |
|
994 return False |
|
995 |
|
996 def __channelModes(self, match): |
|
997 """ |
|
998 Private method to handle a message reporting the channel modes. |
|
999 |
|
1000 @param match match object that matched the pattern |
|
1001 @type re.Match |
|
1002 @return flag indicating whether the message was handled |
|
1003 @rtype bool |
|
1004 """ |
|
1005 if match.group(1).lower() == self.__name.lower(): |
|
1006 modesDict = getChannelModesDict() |
|
1007 modesParameters = match.group(2).split() |
|
1008 modeString = modesParameters.pop(0) |
|
1009 modes = [] |
|
1010 for modeChar in modeString: |
|
1011 if modeChar == "+": |
|
1012 continue |
|
1013 elif modeChar == "k": |
|
1014 parameter = modesParameters.pop(0) |
|
1015 modes.append(self.tr("password protected ({0})").format(parameter)) |
|
1016 elif modeChar == "l": |
|
1017 parameter = modesParameters.pop(0) |
|
1018 modes.append(self.tr("limited to %n user(s)", "", int(parameter))) |
|
1019 elif modeChar in modesDict: |
|
1020 modes.append(modesDict[modeChar]) |
|
1021 else: |
|
1022 modes.append(modeChar) |
|
1023 |
|
1024 self.__addManagementMessage( |
|
1025 IrcChannelWidget.MessageIndicator, |
|
1026 self.tr("Channel modes: {0}.").format(", ".join(modes)), |
|
1027 ) |
|
1028 |
|
1029 return True |
|
1030 |
|
1031 return False |
|
1032 |
|
1033 def __channelCreated(self, match): |
|
1034 """ |
|
1035 Private method to handle a channel created message. |
|
1036 |
|
1037 @param match match object that matched the pattern |
|
1038 @type re.Match |
|
1039 @return flag indicating whether the message was handled |
|
1040 @rtype bool |
|
1041 """ |
|
1042 if match.group(1).lower() == self.__name.lower(): |
|
1043 self.__addManagementMessage( |
|
1044 IrcChannelWidget.MessageIndicator, |
|
1045 self.tr("This channel was created on {0}.").format( |
|
1046 QDateTime.fromSecsSinceEpoch(int(match.group(2))).toString( |
|
1047 "yyyy-MM-dd hh:mm" |
|
1048 ) |
|
1049 ), |
|
1050 ) |
|
1051 return True |
|
1052 |
|
1053 return False |
|
1054 |
|
1055 def __updateChannelModes(self, match): |
|
1056 """ |
|
1057 Private method to handle a message reporting the channel modes. |
|
1058 |
|
1059 @param match match object that matched the pattern |
|
1060 @type re.Match |
|
1061 @return flag indicating whether the message was handled |
|
1062 @rtype bool |
|
1063 """ |
|
1064 # group(1) user or server |
|
1065 # group(2) channel |
|
1066 # group(3) modes and parameter list |
|
1067 if match.group(2).lower() == self.__name.lower(): |
|
1068 nick = match.group(1) |
|
1069 modesParameters = match.group(3).split() |
|
1070 modeString = modesParameters.pop(0) |
|
1071 isPlus = True |
|
1072 message = "" |
|
1073 for mode in modeString: |
|
1074 if mode == "+": |
|
1075 isPlus = True |
|
1076 continue |
|
1077 elif mode == "-": |
|
1078 isPlus = False |
|
1079 continue |
|
1080 elif mode == "a": |
|
1081 if isPlus: |
|
1082 message = self.tr( |
|
1083 "{0} sets the channel mode to 'anonymous'." |
|
1084 ).format(nick) |
|
1085 else: |
|
1086 message = self.tr( |
|
1087 "{0} removes the 'anonymous' mode from the channel." |
|
1088 ).format(nick) |
|
1089 elif mode == "b": |
|
1090 if isPlus: |
|
1091 message = self.tr("{0} sets a ban on {1}.").format( |
|
1092 nick, modesParameters.pop(0) |
|
1093 ) |
|
1094 else: |
|
1095 message = self.tr("{0} removes the ban on {1}.").format( |
|
1096 nick, modesParameters.pop(0) |
|
1097 ) |
|
1098 elif mode == "c": |
|
1099 if isPlus: |
|
1100 message = self.tr( |
|
1101 "{0} sets the channel mode to 'no colors allowed'." |
|
1102 ).format(nick) |
|
1103 else: |
|
1104 message = self.tr( |
|
1105 "{0} sets the channel mode to 'allow color codes'." |
|
1106 ).format(nick) |
|
1107 elif mode == "e": |
|
1108 if isPlus: |
|
1109 message = self.tr("{0} sets a ban exception on {1}.").format( |
|
1110 nick, modesParameters.pop(0) |
|
1111 ) |
|
1112 else: |
|
1113 message = self.tr( |
|
1114 "{0} removes the ban exception on {1}." |
|
1115 ).format(nick, modesParameters.pop(0)) |
|
1116 elif mode == "i": |
|
1117 if isPlus: |
|
1118 message = self.tr( |
|
1119 "{0} sets the channel mode to 'invite only'." |
|
1120 ).format(nick) |
|
1121 else: |
|
1122 message = self.tr( |
|
1123 "{0} removes the 'invite only' mode from the channel." |
|
1124 ).format(nick) |
|
1125 elif mode == "k": |
|
1126 if isPlus: |
|
1127 message = self.tr("{0} sets the channel key to '{1}'.").format( |
|
1128 nick, modesParameters.pop(0) |
|
1129 ) |
|
1130 else: |
|
1131 message = self.tr("{0} removes the channel key.").format(nick) |
|
1132 elif mode == "l": |
|
1133 if isPlus: |
|
1134 message = self.tr( |
|
1135 "{0} sets the channel limit to %n nick(s).", |
|
1136 "", |
|
1137 int(modesParameters.pop(0)), |
|
1138 ).format(nick) |
|
1139 else: |
|
1140 message = self.tr("{0} removes the channel limit.").format(nick) |
|
1141 elif mode == "m": |
|
1142 if isPlus: |
|
1143 message = self.tr( |
|
1144 "{0} sets the channel mode to 'moderated'." |
|
1145 ).format(nick) |
|
1146 else: |
|
1147 message = self.tr( |
|
1148 "{0} sets the channel mode to 'unmoderated'." |
|
1149 ).format(nick) |
|
1150 elif mode == "n": |
|
1151 if isPlus: |
|
1152 message = self.tr( |
|
1153 "{0} sets the channel mode to 'no messages from" |
|
1154 " outside'." |
|
1155 ).format(nick) |
|
1156 else: |
|
1157 message = self.tr( |
|
1158 "{0} sets the channel mode to 'allow messages" |
|
1159 " from outside'." |
|
1160 ).format(nick) |
|
1161 elif mode == "p": |
|
1162 if isPlus: |
|
1163 message = self.tr( |
|
1164 "{0} sets the channel mode to 'private'." |
|
1165 ).format(nick) |
|
1166 else: |
|
1167 message = self.tr( |
|
1168 "{0} sets the channel mode to 'public'." |
|
1169 ).format(nick) |
|
1170 elif mode == "q": |
|
1171 if isPlus: |
|
1172 message = self.tr( |
|
1173 "{0} sets the channel mode to 'quiet'." |
|
1174 ).format(nick) |
|
1175 else: |
|
1176 message = self.tr( |
|
1177 "{0} removes the 'quiet' mode from the channel." |
|
1178 ).format(nick) |
|
1179 elif mode == "r": |
|
1180 continue |
|
1181 elif mode == "s": |
|
1182 if isPlus: |
|
1183 message = self.tr( |
|
1184 "{0} sets the channel mode to 'secret'." |
|
1185 ).format(nick) |
|
1186 else: |
|
1187 message = self.tr( |
|
1188 "{0} sets the channel mode to 'visible'." |
|
1189 ).format(nick) |
|
1190 elif mode == "t": |
|
1191 if isPlus: |
|
1192 message = self.tr("{0} switches on 'topic protection'.").format( |
|
1193 nick |
|
1194 ) |
|
1195 else: |
|
1196 message = self.tr( |
|
1197 "{0} switches off 'topic protection'." |
|
1198 ).format(nick) |
|
1199 elif mode == "I": |
|
1200 if isPlus: |
|
1201 message = self.tr("{0} sets invitation mask {1}.").format( |
|
1202 nick, modesParameters.pop(0) |
|
1203 ) |
|
1204 else: |
|
1205 message = self.tr( |
|
1206 "{0} removes the invitation mask {1}." |
|
1207 ).format(nick, modesParameters.pop(0)) |
|
1208 |
|
1209 self.__addManagementMessage(self.tr("Mode"), message) |
|
1210 |
|
1211 return True |
|
1212 |
|
1213 return False |
|
1214 |
|
1215 def __setUserPrivilege(self, match): |
|
1216 """ |
|
1217 Private method to handle a change of user privileges for the channel. |
|
1218 |
|
1219 @param match match object that matched the pattern |
|
1220 @type re.Match |
|
1221 @return flag indicating whether the message was handled |
|
1222 @rtype bool |
|
1223 """ |
|
1224 if match.group(2).lower() == self.__name.lower(): |
|
1225 itm = self.__findUser(match.group(4)) |
|
1226 if itm: |
|
1227 itm.changePrivilege(match.group(3)) |
|
1228 self.__setEditTopicButton() |
|
1229 self.__addManagementMessage( |
|
1230 IrcChannelWidget.MessageIndicator, |
|
1231 self.tr("{0} sets mode for {1}: {2}.").format( |
|
1232 match.group(1), match.group(4), match.group(3) |
|
1233 ), |
|
1234 ) |
|
1235 return True |
|
1236 |
|
1237 return False |
|
1238 |
|
1239 def __ignore(self, match): |
|
1240 """ |
|
1241 Private method to handle a channel message we are not interested in. |
|
1242 |
|
1243 @param match match object that matched the pattern |
|
1244 @type re.Match |
|
1245 @return flag indicating whether the message was handled |
|
1246 @rtype bool |
|
1247 """ |
|
1248 if match.group(1).lower() == self.__name.lower(): |
|
1249 return True |
|
1250 |
|
1251 return False |
|
1252 |
|
1253 def __help(self, match): |
|
1254 """ |
|
1255 Private method to handle a help message. |
|
1256 |
|
1257 @param match match object that matched the pattern |
|
1258 @type re.Match |
|
1259 @return flag indicating whether the message was handled |
|
1260 @rtype bool |
|
1261 """ |
|
1262 self.__addManagementMessage( |
|
1263 self.tr("Help"), "{0} {1}".format(match.group(1), ircFilter(match.group(2))) |
|
1264 ) |
|
1265 return True |
|
1266 |
|
1267 def __handleCtcp(self, match): |
|
1268 """ |
|
1269 Private method to handle a CTCP channel command. |
|
1270 |
|
1271 @param match reference to the match object |
|
1272 @type re.Match |
|
1273 @return flag indicating, if the message was handled |
|
1274 @rtype bool |
|
1275 """ |
|
1276 # group(1) sender user name |
|
1277 # group(2) sender user@host |
|
1278 # group(3) target nick |
|
1279 # group(4) message |
|
1280 if match.group(4).startswith("\x01"): |
|
1281 ctcpCommand = match.group(4)[1:].split("\x01", 1)[0] |
|
1282 if " " in ctcpCommand: |
|
1283 ctcpRequest, ctcpArg = ctcpCommand.split(" ", 1) |
|
1284 else: |
|
1285 ctcpRequest, ctcpArg = ctcpCommand, "" |
|
1286 ctcpRequest = ctcpRequest.lower() |
|
1287 if ctcpRequest == "version": |
|
1288 msg = "Eric IRC client {0}, {1}".format(Version, Copyright) |
|
1289 self.__addManagementMessage( |
|
1290 self.tr("CTCP"), |
|
1291 self.tr("Received Version request from {0}.").format( |
|
1292 match.group(1) |
|
1293 ), |
|
1294 ) |
|
1295 self.sendCtcpReply.emit(match.group(1), "VERSION " + msg) |
|
1296 elif ctcpRequest == "ping": |
|
1297 self.__addManagementMessage( |
|
1298 self.tr("CTCP"), |
|
1299 self.tr( |
|
1300 "Received CTCP-PING request from {0}, sending answer." |
|
1301 ).format(match.group(1)), |
|
1302 ) |
|
1303 self.sendCtcpReply.emit(match.group(1), "PING {0}".format(ctcpArg)) |
|
1304 elif ctcpRequest == "clientinfo": |
|
1305 self.__addManagementMessage( |
|
1306 self.tr("CTCP"), |
|
1307 self.tr( |
|
1308 "Received CTCP-CLIENTINFO request from {0}, sending answer." |
|
1309 ).format(match.group(1)), |
|
1310 ) |
|
1311 self.sendCtcpReply.emit( |
|
1312 match.group(1), "CLIENTINFO CLIENTINFO PING VERSION" |
|
1313 ) |
|
1314 else: |
|
1315 self.__addManagementMessage( |
|
1316 self.tr("CTCP"), |
|
1317 self.tr("Received unknown CTCP-{0} request from {1}.").format( |
|
1318 ctcpRequest, match.group(1) |
|
1319 ), |
|
1320 ) |
|
1321 return True |
|
1322 |
|
1323 return False |
|
1324 |
|
1325 def setUserPrivilegePrefix(self, prefixes): |
|
1326 """ |
|
1327 Public method to set the user privilege to prefix mapping. |
|
1328 |
|
1329 @param prefixes dictionary with privilege as key and prefix as value |
|
1330 @type dict |
|
1331 """ |
|
1332 self.__prefixToPrivilege = {} |
|
1333 for privilege, prefix in prefixes.items(): |
|
1334 if prefix: |
|
1335 self.__prefixToPrivilege[prefix] = privilege |
|
1336 |
|
1337 def __findUser(self, name): |
|
1338 """ |
|
1339 Private method to find the user in the list of users. |
|
1340 |
|
1341 @param name user name to search for |
|
1342 @type str |
|
1343 @return reference to the list entry |
|
1344 @rtype QListWidgetItem |
|
1345 """ |
|
1346 for row in range(self.usersList.count()): |
|
1347 itm = self.usersList.item(row) |
|
1348 if itm.name() == name: |
|
1349 return itm |
|
1350 |
|
1351 return None |
|
1352 |
|
1353 def __extractPrivilege(self, name): |
|
1354 """ |
|
1355 Private method to extract the user privileges out of the name. |
|
1356 |
|
1357 @param name user name and prefixes |
|
1358 @type str |
|
1359 @return tuple containing a list of privileges and user name |
|
1360 @rtype tuple of (list of str, str) |
|
1361 """ |
|
1362 privileges = [] |
|
1363 while name[0] in self.__prefixToPrivilege: |
|
1364 prefix = name[0] |
|
1365 privileges.append(self.__prefixToPrivilege[prefix]) |
|
1366 name = name[1:] |
|
1367 if name[0] == ",": |
|
1368 name = name[1:] |
|
1369 |
|
1370 return privileges, name |
|
1371 |
|
1372 def __addManagementMessage(self, indicator, message): |
|
1373 """ |
|
1374 Private method to add a channel management message to the list. |
|
1375 |
|
1376 @param indicator indicator to be shown |
|
1377 @type str |
|
1378 @param message message to be shown |
|
1379 @type str |
|
1380 """ |
|
1381 if indicator == self.JoinIndicator: |
|
1382 color = ircExtensionPluginObject.getPreferences("JoinChannelColour") |
|
1383 elif indicator == self.LeaveIndicator: |
|
1384 color = ircExtensionPluginObject.getPreferences("LeaveChannelColour") |
|
1385 else: |
|
1386 color = ircExtensionPluginObject.getPreferences("ChannelInfoColour") |
|
1387 self.__appendMessage( |
|
1388 '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format( |
|
1389 color, ircTimestamp(), indicator, message |
|
1390 ) |
|
1391 ) |
|
1392 |
|
1393 def __appendMessage(self, message): |
|
1394 """ |
|
1395 Private slot to append a message. |
|
1396 |
|
1397 @param message message to be appended |
|
1398 @type str |
|
1399 """ |
|
1400 if ( |
|
1401 self.__hidden |
|
1402 and self.__markerLine == "" |
|
1403 and ircExtensionPluginObject.getPreferences("MarkPositionWhenHidden") |
|
1404 ): |
|
1405 self.setMarkerLine() |
|
1406 self.messages.append(message) |
|
1407 |
|
1408 def setMarkerLine(self): |
|
1409 """ |
|
1410 Public method to draw a line to mark the current position. |
|
1411 """ |
|
1412 self.unsetMarkerLine() |
|
1413 self.__markerLine = ( |
|
1414 '<span style=" color:{0}; background-color:{1};">{2}</span>'.format( |
|
1415 ircExtensionPluginObject.getPreferences("MarkerLineForegroundColour"), |
|
1416 ircExtensionPluginObject.getPreferences("MarkerLineBackgroundColour"), |
|
1417 self.tr("--- New From Here ---"), |
|
1418 ) |
|
1419 ) |
|
1420 self.messages.append(self.__markerLine) |
|
1421 |
|
1422 def unsetMarkerLine(self): |
|
1423 """ |
|
1424 Public method to remove the marker line. |
|
1425 """ |
|
1426 if self.__markerLine: |
|
1427 txt = self.messages.toHtml() |
|
1428 if txt.endswith(self.__markerLine + "</p></body></html>"): |
|
1429 # remove empty last paragraph |
|
1430 pos = txt.rfind("<p") |
|
1431 txt = txt[:pos] + "</body></html>" |
|
1432 else: |
|
1433 txt = txt.replace(self.__markerLine, "") |
|
1434 self.messages.setHtml(txt) |
|
1435 self.__markerLine = "" |
|
1436 self.messages.moveCursor(QTextCursor.MoveOperation.End) |
|
1437 |
|
1438 def __clearMessages(self): |
|
1439 """ |
|
1440 Private slot to clear the contents of the messages display. |
|
1441 """ |
|
1442 self.messages.clear() |
|
1443 |
|
1444 def __copyMessages(self): |
|
1445 """ |
|
1446 Private slot to copy the selection of the messages display to the |
|
1447 clipboard. |
|
1448 """ |
|
1449 self.messages.copy() |
|
1450 |
|
1451 def __copyAllMessages(self): |
|
1452 """ |
|
1453 Private slot to copy the contents of the messages display to the |
|
1454 clipboard. |
|
1455 """ |
|
1456 txt = self.messages.toPlainText() |
|
1457 if txt: |
|
1458 cb = QApplication.clipboard() |
|
1459 cb.setText(txt) |
|
1460 |
|
1461 def __cutAllMessages(self): |
|
1462 """ |
|
1463 Private slot to cut the contents of the messages display to the |
|
1464 clipboard. |
|
1465 """ |
|
1466 txt = self.messages.toPlainText() |
|
1467 if txt: |
|
1468 cb = QApplication.clipboard() |
|
1469 cb.setText(txt) |
|
1470 self.messages.clear() |
|
1471 |
|
1472 def __saveMessages(self): |
|
1473 """ |
|
1474 Private slot to save the contents of the messages display. |
|
1475 """ |
|
1476 hasText = not self.messages.document().isEmpty() |
|
1477 if hasText: |
|
1478 if OSUtilities.isWindowsPlatform(): |
|
1479 htmlExtension = "htm" |
|
1480 else: |
|
1481 htmlExtension = "html" |
|
1482 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
|
1483 self, |
|
1484 self.tr("Save Messages"), |
|
1485 "", |
|
1486 self.tr("HTML Files (*.{0});;Text Files (*.txt);;All Files (*)").format( |
|
1487 htmlExtension |
|
1488 ), |
|
1489 None, |
|
1490 EricFileDialog.DontConfirmOverwrite, |
|
1491 ) |
|
1492 if fname: |
|
1493 fpath = pathlib.Path(fname) |
|
1494 if not fpath.suffix: |
|
1495 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
1496 if ex: |
|
1497 fpath = fpath.with_suffix(ex) |
|
1498 if fpath.exists(): |
|
1499 res = EricMessageBox.yesNo( |
|
1500 self, |
|
1501 self.tr("Save Messages"), |
|
1502 self.tr( |
|
1503 "<p>The file <b>{0}</b> already exists." |
|
1504 " Overwrite it?</p>" |
|
1505 ).format(fpath), |
|
1506 icon=EricMessageBox.Warning, |
|
1507 ) |
|
1508 if not res: |
|
1509 return |
|
1510 |
|
1511 try: |
|
1512 txt = ( |
|
1513 self.messages.toHtml() |
|
1514 if fpath.suffix.lower() in [".htm", ".html"] |
|
1515 else self.messages.toPlainText() |
|
1516 ) |
|
1517 with fpath.open("w", encoding="utf-8") as f: |
|
1518 f.write(txt) |
|
1519 except OSError as err: |
|
1520 EricMessageBox.critical( |
|
1521 self, |
|
1522 self.tr("Error saving Messages"), |
|
1523 self.tr( |
|
1524 """<p>The messages contents could not be written""" |
|
1525 """ to <b>{0}</b></p><p>Reason: {1}</p>""" |
|
1526 ).format(fpath, str(err)), |
|
1527 ) |
|
1528 |
|
1529 def __initMessagesMenu(self): |
|
1530 """ |
|
1531 Private slot to initialize the context menu of the messages pane. |
|
1532 """ |
|
1533 self.__messagesMenu = QMenu(self) |
|
1534 self.__copyMessagesAct = self.__messagesMenu.addAction( |
|
1535 EricPixmapCache.getIcon("editCopy"), self.tr("Copy"), self.__copyMessages |
|
1536 ) |
|
1537 self.__messagesMenu.addSeparator() |
|
1538 self.__cutAllMessagesAct = self.__messagesMenu.addAction( |
|
1539 EricPixmapCache.getIcon("editCut"), |
|
1540 self.tr("Cut all"), |
|
1541 self.__cutAllMessages, |
|
1542 ) |
|
1543 self.__copyAllMessagesAct = self.__messagesMenu.addAction( |
|
1544 EricPixmapCache.getIcon("editCopy"), |
|
1545 self.tr("Copy all"), |
|
1546 self.__copyAllMessages, |
|
1547 ) |
|
1548 self.__messagesMenu.addSeparator() |
|
1549 self.__clearMessagesAct = self.__messagesMenu.addAction( |
|
1550 EricPixmapCache.getIcon("editDelete"), |
|
1551 self.tr("Clear"), |
|
1552 self.__clearMessages, |
|
1553 ) |
|
1554 self.__messagesMenu.addSeparator() |
|
1555 self.__saveMessagesAct = self.__messagesMenu.addAction( |
|
1556 EricPixmapCache.getIcon("fileSave"), self.tr("Save"), self.__saveMessages |
|
1557 ) |
|
1558 self.__messagesMenu.addSeparator() |
|
1559 self.__setMarkerMessagesAct = self.__messagesMenu.addAction( |
|
1560 self.tr("Mark Current Position"), self.setMarkerLine |
|
1561 ) |
|
1562 self.__unsetMarkerMessagesAct = self.__messagesMenu.addAction( |
|
1563 self.tr("Remove Position Marker"), self.unsetMarkerLine |
|
1564 ) |
|
1565 |
|
1566 self.on_messages_copyAvailable(False) |
|
1567 |
|
1568 @pyqtSlot(bool) |
|
1569 def on_messages_copyAvailable(self, yes): |
|
1570 """ |
|
1571 Private slot to react to text selection/deselection of the messages |
|
1572 edit. |
|
1573 |
|
1574 @param yes flag signaling the availability of selected text |
|
1575 @type bool |
|
1576 """ |
|
1577 self.__copyMessagesAct.setEnabled(yes) |
|
1578 |
|
1579 @pyqtSlot(QPoint) |
|
1580 def on_messages_customContextMenuRequested(self, pos): |
|
1581 """ |
|
1582 Private slot to show the context menu of the messages pane. |
|
1583 |
|
1584 @param pos the position of the mouse pointer |
|
1585 @type QPoint |
|
1586 """ |
|
1587 enable = not self.messages.document().isEmpty() |
|
1588 self.__cutAllMessagesAct.setEnabled(enable) |
|
1589 self.__copyAllMessagesAct.setEnabled(enable) |
|
1590 self.__saveMessagesAct.setEnabled(enable) |
|
1591 self.__setMarkerMessagesAct.setEnabled(self.__markerLine == "") |
|
1592 self.__unsetMarkerMessagesAct.setEnabled(self.__markerLine != "") |
|
1593 self.__messagesMenu.popup(self.messages.mapToGlobal(pos)) |
|
1594 |
|
1595 def __whoIs(self): |
|
1596 """ |
|
1597 Private slot to get information about the selected user. |
|
1598 """ |
|
1599 self.__whoIsNick = self.usersList.selectedItems()[0].text() |
|
1600 self.sendData.emit("WHOIS " + self.__whoIsNick) |
|
1601 |
|
1602 def __openPrivateChat(self): |
|
1603 """ |
|
1604 Private slot to open a chat with the selected user. |
|
1605 """ |
|
1606 user = self.usersList.selectedItems()[0].text() |
|
1607 self.openPrivateChat.emit(user) |
|
1608 |
|
1609 def __sendUserMessage(self): |
|
1610 """ |
|
1611 Private slot to send a private message to a specific user. |
|
1612 """ |
|
1613 from eric7.EricWidgets import EricTextInputDialog |
|
1614 |
|
1615 user = self.usersList.selectedItems()[0].text() |
|
1616 ok, message = EricTextInputDialog.getText( |
|
1617 self, |
|
1618 self.tr("Send Message"), |
|
1619 self.tr("Enter the message to be sent:"), |
|
1620 minimumWidth=400, |
|
1621 ) |
|
1622 if ok and message: |
|
1623 self.__processUserMessage("/MSG {0} {1}".format(user, message)) |
|
1624 |
|
1625 def __sendUserQuery(self): |
|
1626 """ |
|
1627 Private slot to send a query message to a specific user. |
|
1628 """ |
|
1629 from eric7.EricWidgets import EricTextInputDialog |
|
1630 |
|
1631 user = self.usersList.selectedItems()[0].text() |
|
1632 ok, message = EricTextInputDialog.getText( |
|
1633 self, |
|
1634 self.tr("Send Query"), |
|
1635 self.tr("Enter the message to be sent:"), |
|
1636 minimumWidth=400, |
|
1637 ) |
|
1638 if ok and message: |
|
1639 self.__processUserMessage("/QUERY {0} {1}".format(user, message)) |
|
1640 |
|
1641 def __sendUserNotice(self): |
|
1642 """ |
|
1643 Private slot to send a notice message to a specific user. |
|
1644 """ |
|
1645 from eric7.EricWidgets import EricTextInputDialog |
|
1646 |
|
1647 user = self.usersList.selectedItems()[0].text() |
|
1648 ok, message = EricTextInputDialog.getText( |
|
1649 self, |
|
1650 self.tr("Send Notice"), |
|
1651 self.tr("Enter the message to be sent:"), |
|
1652 minimumWidth=400, |
|
1653 ) |
|
1654 if ok and message: |
|
1655 self.__processUserMessage("/NOTICE {0} {1}".format(user, message)) |
|
1656 |
|
1657 def __pingUser(self): |
|
1658 """ |
|
1659 Private slot to send a ping to a specific user. |
|
1660 """ |
|
1661 user = self.usersList.selectedItems()[0].text() |
|
1662 self.__processUserMessage("/PING {0}".format(user)) |
|
1663 |
|
1664 def __ignoreUser(self): |
|
1665 """ |
|
1666 Private slot to ignore a specific user. |
|
1667 """ |
|
1668 user = self.usersList.selectedItems()[0].text() |
|
1669 self.__processUserMessage("/IGNORE {0}".format(user)) |
|
1670 |
|
1671 def __initUsersMenu(self): |
|
1672 """ |
|
1673 Private slot to initialize the users list context menu. |
|
1674 """ |
|
1675 self.__usersMenu = QMenu(self) |
|
1676 self.__whoIsAct = self.__usersMenu.addAction(self.tr("Who Is"), self.__whoIs) |
|
1677 self.__usersMenu.addSeparator() |
|
1678 self.__privateChatAct = self.__usersMenu.addAction( |
|
1679 self.tr("Private Chat"), self.__openPrivateChat |
|
1680 ) |
|
1681 self.__usersMenu.addSeparator() |
|
1682 self.__sendUserMessageAct = self.__usersMenu.addAction( |
|
1683 self.tr("Send Message"), self.__sendUserMessage |
|
1684 ) |
|
1685 self.__sendUserQueryAct = self.__usersMenu.addAction( |
|
1686 self.tr("Send Query"), self.__sendUserQuery |
|
1687 ) |
|
1688 self.__sendUserNoticeAct = self.__usersMenu.addAction( |
|
1689 self.tr("Send Notice"), self.__sendUserNotice |
|
1690 ) |
|
1691 self.__usersMenu.addSeparator() |
|
1692 self.__pingUserAct = self.__usersMenu.addAction( |
|
1693 self.tr("Send Ping"), self.__pingUser |
|
1694 ) |
|
1695 self.__ignoreUserAct = self.__usersMenu.addAction( |
|
1696 self.tr("Ignore User"), self.__ignoreUser |
|
1697 ) |
|
1698 self.__usersMenu.addSeparator() |
|
1699 self.__usersListRefreshAct = self.__usersMenu.addAction( |
|
1700 self.tr("Refresh"), self.__sendAutoWhoCommand |
|
1701 ) |
|
1702 |
|
1703 @pyqtSlot(QPoint) |
|
1704 def on_usersList_customContextMenuRequested(self, pos): |
|
1705 """ |
|
1706 Private slot to show the context menu of the users list. |
|
1707 |
|
1708 @param pos the position of the mouse pointer |
|
1709 @type QPoint |
|
1710 """ |
|
1711 enable = len(self.usersList.selectedItems()) > 0 |
|
1712 enablePrivate = enable and not self.__private |
|
1713 itm = self.usersList.itemAt(pos) |
|
1714 if itm and enablePrivate: |
|
1715 enablePrivate = itm.text().lower() not in [ |
|
1716 "chanserv", |
|
1717 self.__userName.lower(), |
|
1718 ] |
|
1719 self.__whoIsAct.setEnabled(enable) |
|
1720 self.__privateChatAct.setEnabled(enablePrivate) |
|
1721 self.__usersListRefreshAct.setEnabled( |
|
1722 self.usersList.count() |
|
1723 <= ircExtensionPluginObject.getPreferences("AutoUserInfoMax") |
|
1724 ) |
|
1725 self.__usersMenu.popup(self.usersList.mapToGlobal(pos)) |
|
1726 |
|
1727 def hideEvent(self, _evt): |
|
1728 """ |
|
1729 Protected method handling hide events. |
|
1730 |
|
1731 @param _evt reference to the hide event (unused) |
|
1732 @type QHideEvent |
|
1733 """ |
|
1734 self.__hidden = True |
|
1735 |
|
1736 def showEvent(self, _evt): |
|
1737 """ |
|
1738 Protected method handling show events. |
|
1739 |
|
1740 @param _evt reference to the show event (unused) |
|
1741 @type QShowEvent |
|
1742 """ |
|
1743 self.__hidden = False |
|
1744 |
|
1745 def initAutoWho(self): |
|
1746 """ |
|
1747 Public method to initialize the Auto Who system. |
|
1748 """ |
|
1749 if ircExtensionPluginObject.getPreferences("AutoUserInfoLookup"): |
|
1750 self.__autoWhoTimer.setInterval( |
|
1751 ircExtensionPluginObject.getPreferences("AutoUserInfoInterval") * 1000 |
|
1752 ) |
|
1753 self.__autoWhoTimer.start() |
|
1754 |
|
1755 @pyqtSlot() |
|
1756 def __sendAutoWhoCommand(self): |
|
1757 """ |
|
1758 Private slot to send the WHO command to update the users list. |
|
1759 """ |
|
1760 if self.usersList.count() <= ircExtensionPluginObject.getPreferences( |
|
1761 "AutoUserInfoMax" |
|
1762 ): |
|
1763 self.__autoWhoRequested = True |
|
1764 self.sendData.emit(self.__autoWhoTemplate.format(self.__name)) |
|
1765 |
|
1766 def __autoWhoEntry(self, match): |
|
1767 """ |
|
1768 Private method to handle a WHO entry returned by the server as |
|
1769 requested automatically. |
|
1770 |
|
1771 @param match match object that matched the pattern |
|
1772 @type re.Match |
|
1773 @return flag indicating whether the message was handled |
|
1774 @rtype bool |
|
1775 """ |
|
1776 # group(1) nick |
|
1777 # group(2) user flags |
|
1778 if self.__autoWhoRequested: |
|
1779 itm = self.__findUser(match.group(1)) |
|
1780 if itm: |
|
1781 itm.parseWhoFlags(match.group(2)) |
|
1782 return True |
|
1783 |
|
1784 return False |
|
1785 |
|
1786 def __whoEnd(self, match): |
|
1787 """ |
|
1788 Private method to handle the end of the WHO list. |
|
1789 |
|
1790 @param match match object that matched the pattern |
|
1791 @type re.Match |
|
1792 @return flag indicating whether the message was handled |
|
1793 @rtype bool |
|
1794 """ |
|
1795 if match.group(1).lower() == self.__name.lower(): |
|
1796 if self.__autoWhoRequested: |
|
1797 self.__autoWhoRequested = False |
|
1798 self.initAutoWho() |
|
1799 else: |
|
1800 self.__addManagementMessage( |
|
1801 self.tr("Who"), |
|
1802 self.tr("End of WHO list for {0}.").format(match.group(1)), |
|
1803 ) |
|
1804 return True |
|
1805 |
|
1806 return False |
|
1807 |
|
1808 def __whoEntry(self, match): |
|
1809 """ |
|
1810 Private method to handle a WHO entry returned by the server as |
|
1811 requested manually. |
|
1812 |
|
1813 @param match match object that matched the pattern |
|
1814 @type re.Match |
|
1815 @return flag indicating whether the message was handled |
|
1816 @rtype bool |
|
1817 """ |
|
1818 # group(1) channel |
|
1819 # group(2) user |
|
1820 # group(3) host |
|
1821 # group(4) nick |
|
1822 # group(5) user flags |
|
1823 # group(6) real name |
|
1824 if match.group(1).lower() == self.__name.lower(): |
|
1825 away = self.tr(" (Away)") if match.group(5).startswith("G") else "" |
|
1826 self.__addManagementMessage( |
|
1827 self.tr("Who"), |
|
1828 self.tr("{0} is {1}@{2} ({3}){4}").format( |
|
1829 match.group(4), match.group(2), match.group(3), match.group(6), away |
|
1830 ), |
|
1831 ) |
|
1832 return True |
|
1833 |
|
1834 return False |
|
1835 |
|
1836 def __whoIsUser(self, match): |
|
1837 """ |
|
1838 Private method to handle the WHOIS user reply. |
|
1839 |
|
1840 @param match match object that matched the pattern |
|
1841 @type re.Match |
|
1842 @return flag indicating whether the message was handled |
|
1843 @rtype bool |
|
1844 """ |
|
1845 # group(1) nick |
|
1846 # group(2) user |
|
1847 # group(3) host |
|
1848 # group(4) real name |
|
1849 if match.group(1) == self.__whoIsNick: |
|
1850 realName = match.group(4).replace("<", "<").replace(">", ">") |
|
1851 self.__addManagementMessage( |
|
1852 self.tr("Whois"), |
|
1853 self.tr("{0} is {1}@{2} ({3}).").format( |
|
1854 match.group(1), match.group(2), match.group(3), realName |
|
1855 ), |
|
1856 ) |
|
1857 return True |
|
1858 |
|
1859 return False |
|
1860 |
|
1861 def __whoIsChannels(self, match): |
|
1862 """ |
|
1863 Private method to handle the WHOIS channels reply. |
|
1864 |
|
1865 @param match match object that matched the pattern |
|
1866 @type re.Match |
|
1867 @return flag indicating whether the message was handled |
|
1868 @rtype bool |
|
1869 """ |
|
1870 # group(1) nick |
|
1871 # group(2) channels |
|
1872 if match.group(1) == self.__whoIsNick: |
|
1873 userChannels = [] |
|
1874 voiceChannels = [] |
|
1875 opChannels = [] |
|
1876 halfopChannels = [] |
|
1877 ownerChannels = [] |
|
1878 adminChannels = [] |
|
1879 |
|
1880 # generate the list of channels the user is in |
|
1881 channelList = match.group(2).split() |
|
1882 for channel in channelList: |
|
1883 if channel.startswith(("*", "&")): |
|
1884 adminChannels.append(channel[1:]) |
|
1885 elif channel.startswith(("!", "~")) and self.__ircWidget.isChannelName( |
|
1886 channel[1:] |
|
1887 ): |
|
1888 ownerChannels.append(channel[1:]) |
|
1889 elif channel.startswith("@+"): |
|
1890 opChannels.append(channel[2:]) |
|
1891 elif channel.startswith("@"): |
|
1892 opChannels.append(channel[1:]) |
|
1893 elif channel.startswith("%"): |
|
1894 halfopChannels.append(channel[1:]) |
|
1895 elif channel.startswith("+"): |
|
1896 voiceChannels.append(channel[1:]) |
|
1897 else: |
|
1898 userChannels.append(channel) |
|
1899 |
|
1900 # show messages |
|
1901 if userChannels: |
|
1902 self.__addManagementMessage( |
|
1903 self.tr("Whois"), |
|
1904 self.tr("{0} is a user on channels: {1}").format( |
|
1905 match.group(1), " ".join(userChannels) |
|
1906 ), |
|
1907 ) |
|
1908 if voiceChannels: |
|
1909 self.__addManagementMessage( |
|
1910 self.tr("Whois"), |
|
1911 self.tr("{0} has voice on channels: {1}").format( |
|
1912 match.group(1), " ".join(voiceChannels) |
|
1913 ), |
|
1914 ) |
|
1915 if halfopChannels: |
|
1916 self.__addManagementMessage( |
|
1917 self.tr("Whois"), |
|
1918 self.tr("{0} is a halfop on channels: {1}").format( |
|
1919 match.group(1), " ".join(halfopChannels) |
|
1920 ), |
|
1921 ) |
|
1922 if opChannels: |
|
1923 self.__addManagementMessage( |
|
1924 self.tr("Whois"), |
|
1925 self.tr("{0} is an operator on channels: {1}").format( |
|
1926 match.group(1), " ".join(opChannels) |
|
1927 ), |
|
1928 ) |
|
1929 if ownerChannels: |
|
1930 self.__addManagementMessage( |
|
1931 self.tr("Whois"), |
|
1932 self.tr("{0} is owner of channels: {1}").format( |
|
1933 match.group(1), " ".join(ownerChannels) |
|
1934 ), |
|
1935 ) |
|
1936 if adminChannels: |
|
1937 self.__addManagementMessage( |
|
1938 self.tr("Whois"), |
|
1939 self.tr("{0} is admin on channels: {1}").format( |
|
1940 match.group(1), " ".join(adminChannels) |
|
1941 ), |
|
1942 ) |
|
1943 return True |
|
1944 |
|
1945 return False |
|
1946 |
|
1947 def __whoIsServer(self, match): |
|
1948 """ |
|
1949 Private method to handle the WHOIS server reply. |
|
1950 |
|
1951 @param match match object that matched the pattern |
|
1952 @type re.Match |
|
1953 @return flag indicating whether the message was handled |
|
1954 @rtype bool |
|
1955 """ |
|
1956 # group(1) nick |
|
1957 # group(2) server |
|
1958 # group(3) server info |
|
1959 if match.group(1) == self.__whoIsNick: |
|
1960 self.__addManagementMessage( |
|
1961 self.tr("Whois"), |
|
1962 self.tr("{0} is online via {1} ({2}).").format( |
|
1963 match.group(1), match.group(2), match.group(3) |
|
1964 ), |
|
1965 ) |
|
1966 return True |
|
1967 |
|
1968 return False |
|
1969 |
|
1970 def __whoIsOperator(self, match): |
|
1971 """ |
|
1972 Private method to handle the WHOIS operator reply. |
|
1973 |
|
1974 @param match match object that matched the pattern |
|
1975 @type re.Match |
|
1976 @return flag indicating whether the message was handled |
|
1977 @rtype bool |
|
1978 """ |
|
1979 # group(1) nick |
|
1980 # group(2) message |
|
1981 if match.group(1) == self.__whoIsNick: |
|
1982 if match.group(2).lower().startswith("is an irc operator"): |
|
1983 self.__addManagementMessage( |
|
1984 self.tr("Whois"), |
|
1985 self.tr("{0} is an IRC Operator.").format(match.group(1)), |
|
1986 ) |
|
1987 else: |
|
1988 self.__addManagementMessage( |
|
1989 self.tr("Whois"), "{0} {1}".format(match.group(1), match.group(2)) |
|
1990 ) |
|
1991 return True |
|
1992 |
|
1993 return False |
|
1994 |
|
1995 def __whoIsIdle(self, match): |
|
1996 """ |
|
1997 Private method to handle the WHOIS idle reply. |
|
1998 |
|
1999 @param match match object that matched the pattern |
|
2000 @type re.Match |
|
2001 @return flag indicating whether the message was handled |
|
2002 @rtype bool |
|
2003 """ |
|
2004 # group(1) nick |
|
2005 # group(2) idle seconds |
|
2006 # group(3) signon time |
|
2007 if match.group(1) == self.__whoIsNick: |
|
2008 seconds = int(match.group(2)) |
|
2009 minutes = seconds // 60 |
|
2010 hours = minutes // 60 |
|
2011 days = hours // 24 |
|
2012 |
|
2013 signonTimestamp = int(match.group(3)) |
|
2014 signonTime = QDateTime() |
|
2015 signonTime.setTime_t(signonTimestamp) |
|
2016 |
|
2017 if days: |
|
2018 daysString = self.tr("%n day(s)", "", days) |
|
2019 hoursString = self.tr("%n hour(s)", "", hours) |
|
2020 minutesString = self.tr("%n minute(s)", "", minutes) |
|
2021 secondsString = self.tr("%n second(s)", "", seconds) |
|
2022 self.__addManagementMessage( |
|
2023 self.tr("Whois"), |
|
2024 self.tr( |
|
2025 "{0} has been idle for {1}, {2}, {3}, and {4}.", |
|
2026 "{0} = name of person, {1} = (x days)," |
|
2027 " {2} = (x hours), {3} = (x minutes)," |
|
2028 " {4} = (x seconds)", |
|
2029 ).format( |
|
2030 match.group(1), |
|
2031 daysString, |
|
2032 hoursString, |
|
2033 minutesString, |
|
2034 secondsString, |
|
2035 ), |
|
2036 ) |
|
2037 elif hours: |
|
2038 hoursString = self.tr("%n hour(s)", "", hours) |
|
2039 minutesString = self.tr("%n minute(s)", "", minutes) |
|
2040 secondsString = self.tr("%n second(s)", "", seconds) |
|
2041 self.__addManagementMessage( |
|
2042 self.tr("Whois"), |
|
2043 self.tr( |
|
2044 "{0} has been idle for {1}, {2}, and {3}.", |
|
2045 "{0} = name of person, {1} = (x hours), " |
|
2046 "{2} = (x minutes), {3} = (x seconds)", |
|
2047 ).format(match.group(1), hoursString, minutesString, secondsString), |
|
2048 ) |
|
2049 elif minutes: |
|
2050 minutesString = self.tr("%n minute(s)", "", minutes) |
|
2051 secondsString = self.tr("%n second(s)", "", seconds) |
|
2052 self.__addManagementMessage( |
|
2053 self.tr("Whois"), |
|
2054 self.tr( |
|
2055 "{0} has been idle for {1} and {2}.", |
|
2056 "{0} = name of person, {1} = (x minutes), {3} = (x seconds)", |
|
2057 ).format(match.group(1), minutesString, secondsString), |
|
2058 ) |
|
2059 else: |
|
2060 self.__addManagementMessage( |
|
2061 self.tr("Whois"), |
|
2062 self.tr("{0} has been idle for %n second(s).", "", seconds).format( |
|
2063 match.group(1) |
|
2064 ), |
|
2065 ) |
|
2066 |
|
2067 if not signonTime.isNull(): |
|
2068 self.__addManagementMessage( |
|
2069 self.tr("Whois"), |
|
2070 self.tr("{0} has been online since {1}.").format( |
|
2071 match.group(1), signonTime.toString("yyyy-MM-dd, hh:mm:ss") |
|
2072 ), |
|
2073 ) |
|
2074 return True |
|
2075 |
|
2076 return False |
|
2077 |
|
2078 def __whoIsEnd(self, match): |
|
2079 """ |
|
2080 Private method to handle the end of WHOIS reply. |
|
2081 |
|
2082 @param match match object that matched the pattern |
|
2083 @type re.Match |
|
2084 @return flag indicating whether the message was handled |
|
2085 @rtype bool |
|
2086 """ |
|
2087 # group(1) nick |
|
2088 # group(2) end message |
|
2089 if match.group(1) == self.__whoIsNick: |
|
2090 self.__whoIsNick = "" |
|
2091 self.__addManagementMessage( |
|
2092 self.tr("Whois"), |
|
2093 self.tr("End of WHOIS list for {0}.").format(match.group(1)), |
|
2094 ) |
|
2095 return True |
|
2096 |
|
2097 return False |
|
2098 |
|
2099 def __whoIsIdentify(self, match): |
|
2100 """ |
|
2101 Private method to handle the WHOIS identify and identified replies. |
|
2102 |
|
2103 @param match match object that matched the pattern |
|
2104 @type re.Match |
|
2105 @return flag indicating whether the message was handled |
|
2106 @rtype bool |
|
2107 """ |
|
2108 # group(1) nick |
|
2109 # group(2) identified message |
|
2110 if match.group(1) == self.__whoIsNick: |
|
2111 self.__addManagementMessage( |
|
2112 self.tr("Whois"), |
|
2113 self.tr("{0} is an identified user.").format(match.group(1)), |
|
2114 ) |
|
2115 return True |
|
2116 |
|
2117 return False |
|
2118 |
|
2119 def __whoIsHelper(self, match): |
|
2120 """ |
|
2121 Private method to handle the WHOIS helper reply. |
|
2122 |
|
2123 @param match match object that matched the pattern |
|
2124 @type re.Match |
|
2125 @return flag indicating whether the message was handled |
|
2126 @rtype bool |
|
2127 """ |
|
2128 # group(1) nick |
|
2129 # group(2) helper message |
|
2130 if match.group(1) == self.__whoIsNick: |
|
2131 self.__addManagementMessage( |
|
2132 self.tr("Whois"), |
|
2133 self.tr("{0} is available for help.").format(match.group(1)), |
|
2134 ) |
|
2135 return True |
|
2136 |
|
2137 return False |
|
2138 |
|
2139 def __whoIsAccount(self, match): |
|
2140 """ |
|
2141 Private method to handle the WHOIS account reply. |
|
2142 |
|
2143 @param match match object that matched the pattern |
|
2144 @type re.Match |
|
2145 @return flag indicating whether the message was handled |
|
2146 @rtype bool |
|
2147 """ |
|
2148 # group(1) nick |
|
2149 # group(2) login name |
|
2150 if match.group(1) == self.__whoIsNick: |
|
2151 self.__addManagementMessage( |
|
2152 self.tr("Whois"), |
|
2153 self.tr("{0} is logged in as {1}.").format( |
|
2154 match.group(1), match.group(2) |
|
2155 ), |
|
2156 ) |
|
2157 return True |
|
2158 |
|
2159 return False |
|
2160 |
|
2161 def __whoIsActually(self, match): |
|
2162 """ |
|
2163 Private method to handle the WHOIS actually reply. |
|
2164 |
|
2165 @param match match object that matched the pattern |
|
2166 @type re.Match |
|
2167 @return flag indicating whether the message was handled |
|
2168 @rtype bool |
|
2169 """ |
|
2170 # group(1) nick |
|
2171 # group(2) actual user@host |
|
2172 # group(3) actual IP |
|
2173 if match.group(1) == self.__whoIsNick: |
|
2174 self.__addManagementMessage( |
|
2175 self.tr("Whois"), |
|
2176 self.tr("{0} is actually using the host {1} (IP: {2}).").format( |
|
2177 match.group(1), match.group(2), match.group(3) |
|
2178 ), |
|
2179 ) |
|
2180 return True |
|
2181 |
|
2182 return False |
|
2183 |
|
2184 def __whoIsSecure(self, match): |
|
2185 """ |
|
2186 Private method to handle the WHOIS secure reply. |
|
2187 |
|
2188 @param match match object that matched the pattern |
|
2189 @type re.Match |
|
2190 @return flag indicating whether the message was handled |
|
2191 @rtype bool |
|
2192 """ |
|
2193 # group(1) nick |
|
2194 if match.group(1) == self.__whoIsNick: |
|
2195 self.__addManagementMessage( |
|
2196 self.tr("Whois"), |
|
2197 self.tr("{0} is using a secure connection.").format(match.group(1)), |
|
2198 ) |
|
2199 return True |
|
2200 |
|
2201 return False |
|
2202 |
|
2203 def __whoIsConnection(self, match): |
|
2204 """ |
|
2205 Private method to handle the WHOIS connection reply. |
|
2206 |
|
2207 @param match match object that matched the pattern |
|
2208 @type re.Match |
|
2209 @return flag indicating whether the message was handled |
|
2210 @rtype bool |
|
2211 """ |
|
2212 # group(1) nick |
|
2213 # group(2) host name |
|
2214 # group(3) IP |
|
2215 if match.group(1) == self.__whoIsNick: |
|
2216 self.__addManagementMessage( |
|
2217 self.tr("Whois"), |
|
2218 self.tr("{0} is connecting from {1} (IP: {2}).").format( |
|
2219 match.group(1), match.group(2), match.group(3) |
|
2220 ), |
|
2221 ) |
|
2222 return True |
|
2223 |
|
2224 return False |
|
2225 |
|
2226 def __setEditTopicButton(self): |
|
2227 """ |
|
2228 Private method to set the visibility of the Edit Topic button. |
|
2229 """ |
|
2230 itm = self.__findUser(self.__userName) |
|
2231 if itm: |
|
2232 self.editTopicButton.setVisible(itm.canChangeTopic()) |
|
2233 |
|
2234 @pyqtSlot() |
|
2235 def on_editTopicButton_clicked(self): |
|
2236 """ |
|
2237 Private slot to change the topic of the channel. |
|
2238 """ |
|
2239 topic, ok = QInputDialog.getText( |
|
2240 self, |
|
2241 self.tr("Edit Channel Topic"), |
|
2242 self.tr("Enter the topic for this channel:"), |
|
2243 QLineEdit.EchoMode.Normal, |
|
2244 self.topicLabel.text(), |
|
2245 ) |
|
2246 if ok and topic != "": |
|
2247 self.sendData.emit("TOPIC {0} :{1}".format(self.__name, topic)) |
|
2248 |
|
2249 @pyqtSlot(QUrl) |
|
2250 def on_messages_anchorClicked(self, url): |
|
2251 """ |
|
2252 Private slot to open links in the default browser. |
|
2253 |
|
2254 @param url URL to be opened |
|
2255 @type QUrl |
|
2256 """ |
|
2257 QDesktopServices.openUrl(url) |