|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing functions used by several IRC objects. |
|
8 """ |
|
9 |
|
10 import re |
|
11 |
|
12 from PyQt6.QtCore import QTime, QCoreApplication |
|
13 from PyQt6.QtWidgets import QApplication |
|
14 |
|
15 import Utilities |
|
16 import Preferences |
|
17 |
|
18 |
|
19 __UrlRe = re.compile( |
|
20 r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+""" |
|
21 r"""(?:[\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)""") |
|
22 __ColorRe = re.compile( |
|
23 r"""((?:\x03(?:0[0-9]|1[0-5]|[0-9])?(?:,(?:0[0-9]|1[0-5]|[0-9]))?)""" |
|
24 r"""|\x02|\x03|\x13|\x15|\x16|\x17|\x1d|\x1f)""") |
|
25 |
|
26 |
|
27 def ircTimestamp(): |
|
28 """ |
|
29 Module method to generate a time stamp string. |
|
30 |
|
31 @return time stamp (string) |
|
32 """ |
|
33 if Preferences.getIrc("ShowTimestamps"): |
|
34 if Preferences.getIrc("TimestampIncludeDate"): |
|
35 if QApplication.isLeftToRight(): |
|
36 f = "{0} {1}" |
|
37 else: |
|
38 f = "{1} {0}" |
|
39 formatString = f.format(Preferences.getIrc("DateFormat"), |
|
40 Preferences.getIrc("TimeFormat")) |
|
41 else: |
|
42 formatString = Preferences.getIrc("TimeFormat") |
|
43 return '<font color="{0}">[{1}]</font> '.format( |
|
44 Preferences.getIrc("TimestampColour"), |
|
45 QTime.currentTime().toString(formatString)) |
|
46 else: |
|
47 return "" |
|
48 |
|
49 |
|
50 def ircFilter(msg): |
|
51 """ |
|
52 Module method to make the message HTML compliant and detect URLs. |
|
53 |
|
54 @param msg message to process (string) |
|
55 @return processed message (string) |
|
56 """ |
|
57 # step 1: cleanup message |
|
58 msg = Utilities.html_encode(msg) |
|
59 |
|
60 # step 2: replace IRC formatting characters |
|
61 openTags = [] |
|
62 parts = __ColorRe.split(msg) |
|
63 msgParts = [] |
|
64 for part in parts: |
|
65 if part == "\x02": # bold |
|
66 if openTags and openTags[-1] == "b": |
|
67 msgParts.append("</" + openTags.pop(-1) + ">") |
|
68 else: |
|
69 msgParts.append("<b>") |
|
70 openTags.append("b") |
|
71 elif part in ["\x03", "\x17"]: |
|
72 if Preferences.getIrc("EnableIrcColours"): |
|
73 if openTags and openTags[-1] == "span": |
|
74 msgParts.append("</" + openTags.pop(-1) + ">") |
|
75 else: |
|
76 continue |
|
77 else: |
|
78 continue |
|
79 elif part == "\x0f": # reset |
|
80 while openTags: |
|
81 msgParts.append("</" + openTags.pop(-1) + ">") |
|
82 elif part == "\x13": # strikethru |
|
83 if openTags and openTags[-1] == "s": |
|
84 msgParts.append("</" + openTags.pop(-1) + ">") |
|
85 else: |
|
86 msgParts.append("<s>") |
|
87 openTags.append("s") |
|
88 elif part in ["\x15", "\x1f"]: # underline |
|
89 if openTags and openTags[-1] == "u": |
|
90 msgParts.append("</" + openTags.pop(-1) + ">") |
|
91 else: |
|
92 msgParts.append("<u>") |
|
93 openTags.append("u") |
|
94 elif part == "\x16": |
|
95 # revert color not supported |
|
96 continue |
|
97 elif part == "\x1d": # italic |
|
98 if openTags and openTags[-1] == "i": |
|
99 msgParts.append("</" + openTags.pop(-1) + ">") |
|
100 else: |
|
101 msgParts.append("<i>") |
|
102 openTags.append("i") |
|
103 elif part.startswith("\x03"): |
|
104 if Preferences.getIrc("EnableIrcColours"): |
|
105 colors = part[1:].split(",", 1) |
|
106 if len(colors) == 1: |
|
107 # foreground color only |
|
108 tag = '<span style="color:{0}">'.format(Preferences.getIrc( |
|
109 "IrcColor{0}".format(int(colors[0])))) |
|
110 else: |
|
111 if colors[0]: |
|
112 # foreground and background |
|
113 tag = ( |
|
114 '<span style="background-color:{0};color={1}">' |
|
115 .format(Preferences.getIrc( |
|
116 "IrcColor{0}".format(int(colors[0]))), |
|
117 Preferences.getIrc( |
|
118 "IrcColor{0}".format(int(colors[1])))) |
|
119 ) |
|
120 else: |
|
121 # background only |
|
122 tag = '<span style="background-color:{0}">'.format( |
|
123 Preferences.getIrc( |
|
124 "IrcColor{0}".format(int(colors[1])))) |
|
125 msgParts.append(tag) |
|
126 openTags.append("span") |
|
127 else: |
|
128 continue |
|
129 else: |
|
130 msgParts.append(part) |
|
131 msg = "".join(msgParts) |
|
132 |
|
133 # step 3: find http and https links |
|
134 parts = __UrlRe.split(msg) |
|
135 msgParts = [] |
|
136 for part in parts: |
|
137 if part.startswith(("http://", "https://", "ftp://")): |
|
138 msgParts.append('<a href="{0}" style="color:{1}">{0}</a>'.format( |
|
139 part, Preferences.getIrc("HyperlinkColour"))) |
|
140 else: |
|
141 msgParts.append(part) |
|
142 |
|
143 return "".join(msgParts) |
|
144 |
|
145 |
|
146 __channelModesDict = None |
|
147 |
|
148 |
|
149 def __initChannelModesDict(): |
|
150 """ |
|
151 Private module function to initialize the channels modes dictionary. |
|
152 """ |
|
153 global __channelModesDict |
|
154 |
|
155 modesDict = { |
|
156 "a": QCoreApplication.translate("IrcUtilities", "anonymous"), |
|
157 "b": QCoreApplication.translate("IrcUtilities", "ban mask"), |
|
158 "c": QCoreApplication.translate("IrcUtilities", "no colors allowed"), |
|
159 "e": QCoreApplication.translate("IrcUtilities", "ban exception mask"), |
|
160 "i": QCoreApplication.translate("IrcUtilities", "invite only"), |
|
161 "k": QCoreApplication.translate("IrcUtilities", "password protected"), |
|
162 "l": QCoreApplication.translate("IrcUtilities", "user limit"), |
|
163 "m": QCoreApplication.translate("IrcUtilities", "moderated"), |
|
164 "n": QCoreApplication.translate("IrcUtilities", |
|
165 "no messages from outside"), |
|
166 "p": QCoreApplication.translate("IrcUtilities", "private"), |
|
167 "q": QCoreApplication.translate("IrcUtilities", "quiet"), |
|
168 "r": QCoreApplication.translate("IrcUtilities", "reop channel"), |
|
169 "s": QCoreApplication.translate("IrcUtilities", "secret"), |
|
170 "t": QCoreApplication.translate("IrcUtilities", "topic protection"), |
|
171 "I": QCoreApplication.translate("IrcUtilities", "invitation mask"), |
|
172 } |
|
173 __channelModesDict = modesDict |
|
174 |
|
175 |
|
176 def getChannelModesDict(): |
|
177 """ |
|
178 Module function to get the dictionary with the channel modes mappings. |
|
179 |
|
180 @return dictionary with channel modes mapping (dict) |
|
181 """ |
|
182 global __channelModesDict |
|
183 |
|
184 if __channelModesDict is None: |
|
185 __initChannelModesDict() |
|
186 |
|
187 return __channelModesDict |