1import re
2from time import time
3
4from gi.repository import GObject
5
6from pychess.ic import IC_STATUS_OFFLINE, IC_STATUS_ACTIVE, IC_STATUS_PLAYING, IC_STATUS_BUSY, \
7    GAME_TYPES_BY_FICS_NAME, BLKCMD_FINGER
8from pychess.Utils.const import WHITE, BLACK
9from pychess.System.Log import log
10
11types = "(?:blitz|standard|lightning|wild|bughouse|crazyhouse|suicide|losers|atomic)"
12rated = "(rated|unrated)"
13colors = r"(?:\[(white|black)\]\s?)?"
14ratings = r"([\d\+\-]{1,4})"
15titleslist = r"(?:GM|IM|FM|WGM|WIM|WFM|TM|SR|TD|SR|CA|C|U|D|B|T|\*)"
16titles = r"((?:\(%s\))+)?" % titleslist
17names = r"(\w+)%s" % titles
18mf = r"(?:([mf]{1,2})\s?)?"
19# FIXME: Needs support for day, hour, min, sec
20times = "[, ]*".join(r"(?:(\d+) %s)?" % s
21                     for s in ("days", "hrs", "mins", "secs"))
22
23# "73 days, 5 hrs, 55 mins"
24# ('73', '5', '55', None)
25
26
27class FingerObject:
28    def __init__(self, name=""):
29        self.__fingerTime = time()
30
31        self.__name = name
32        self.__status = None
33        self.__upTime = 0
34        self.__idleTime = 0
35        self.__busyMessage = ""
36        self.__lastSeen = 0
37        self.__totalTimeOnline = 0
38        self.__created = 0  # Since field from % of life online
39        self.__email = ""
40        self.__sanctions = ""
41        self.__adminLevel = ""
42        self.__timeseal = False
43        self.__notes = [""] * 10
44        self.__gameno = ""
45        self.__color = WHITE
46        self.__opponent = ""
47        self.__silence = False
48        self.__titles = None
49
50        self.__ratings = {}
51
52    def getName(self):
53        """ Returns the name of the user, without any title sufixes """
54        return self.__name
55
56    def getStatus(self):
57        """ Returns the current user-status as a STATUS constant """
58        return self.__status
59
60    def getUpTime(self):
61        """ Returns the when the user logged on
62            Not set when status == STATUS_OFFLINE """
63        return self.__upTime + time() - self.__fingerTime
64
65    def getIdleTime(self):
66        """ Returns the when the last time the user did something active
67            Not set when status == STATUS_OFFLINE """
68        return self.__idleTime + time() - self.__fingerTime
69
70    def getBusyMessage(self):
71        """ Returns the userset idle message
72            This is set when status == STATUS_BUSY or sometimes when status ==
73            STATUS_PLAYING """
74        return self.__busyMessage
75
76    def getLastSeen(self):
77        """ Returns when the user logged off
78            This is only set when status == STATUS_OFFLINE
79            This is not set, if the user has never logged on """
80        return self.__lastSeen
81
82    def getTotalTimeOnline(self):
83        """ Returns how many seconds the user has been on FICS since the account
84            was created.
85            This is not set, if the user has never logged on """
86        return self.__totalTimeOnline
87
88    def getCreated(self):
89        """ Returns when the account was created """
90        return self.__created
91
92    def getEmail(self):
93        """ Returns the email adress of the user.
94            This will probably only be set for the logged in user """
95        return self.__email
96
97    def getSanctions(self):
98        """ Returns any sanctions the user has against them. This is usually
99            an empty string """
100        return self.__sanctions
101
102    def getAdminLevel(self):
103        """ Returns the admin level as a string
104            Only set for admins. """
105        return self.__adminLevel
106
107    def getTimeseal(self):
108        """ Returns True if the user is using timeseal for fics connection """
109        return self.__timeseal
110
111    def getNotes(self):
112        """ Returns a list of the ten finger notes """
113        return self.__notes
114
115    def getGameno(self):
116        """ Returns the gameno of the game in which user is currently playing
117            This is only set when status == STATUS_PLAYING """
118        return self.__gameno
119
120    def getColor(self):
121        """ If status == STATUS_PLAYING getColor returns the color witch the
122            player has got in the game.
123            Otherwise always WHITE is returned """
124        return self.__color
125
126    def getOpponent(self):
127        """ Returns the opponent of the user in his current game
128            This is only set when status == STATUS_PLAYING """
129        return self.__opponent
130
131    def getSilence(self):
132        """ Return True if the user is playing in silence
133            This is only set when status == STATUS_PLAYING """
134        return self.__silence
135
136    def getRatings(self):
137        return self.__ratings
138
139    def getRating(self, type=None):
140        return int(self.__ratings[type][0])
141
142    def getRatingsLen(self):
143        return len(self.__ratings)
144
145    def getTitles(self):
146        return self.__titles
147
148    def setName(self, value):
149        self.__name = value
150
151    def setStatus(self, value):
152        self.__status = value
153
154    def setUpTime(self, value):
155        """ Use relative seconds """
156        self.__upTime = value
157
158    def setIdleTime(self, value):
159        """ Use relative seconds """
160        self.__idleTime = value
161
162    def setBusyMessage(self, value):
163        """ Use relative seconds """
164        self.__busyMessage = value
165
166    def setLastSeen(self, value):
167        """ Use relative seconds """
168        self.__lastSeen = value
169
170    def setTotalTimeOnline(self, value):
171        """ Use relative seconds """
172        self.__totalTimeOnline = value
173
174    def setCreated(self, value):
175        """ Use relative seconds """
176        self.__created = value
177
178    def setEmail(self, value):
179        self.__email = value
180
181    def setSanctions(self, value):
182        self.__sanctions = value
183
184    def setAdminLevel(self, value):
185        self.__adminLevel = value
186
187    def setTimeseal(self, value):
188        self.__timeseal = value
189
190    def setNote(self, index, note):
191        self.__notes[index] = note
192
193    def setGameno(self, value):
194        self.__gameno = value
195
196    def setColor(self, value):
197        self.__color = value
198
199    def setOpponent(self, value):
200        self.__opponent = value
201
202    def setSilence(self, value):
203        self.__silence = value
204
205    def setRating(self, rating_type, rating_line):
206        self.__ratings[rating_type] = rating_line
207
208    def setTitles(self, titles):
209        self.__titles = titles
210
211
212class FingerManager(GObject.GObject):
213
214    __gsignals__ = {
215        'fingeringFinished': (GObject.SignalFlags.RUN_FIRST, None, (object, )),
216        'ratingAdjusted': (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
217    }
218
219    def __init__(self, connection):
220        GObject.GObject.__init__(self)
221        self.connection = connection
222
223        fingerLines = (
224            r"(?P<never>%s has never connected\.)" % names,
225            "Last disconnected: (?P<last>.+)",
226            "On for: (?P<uptime>.+?) +Idle: (?P<idletime>.+)",
227            r"%s is in (?P<silence>silence) mode\." % names,
228            r"\(playing game (?P<gameno>\d+): (?P<p1>\S+?)%s vs. (?P<p2>\S+?)%s\)"
229            % (titles, titles), r"\(%s (?P<busymessage>.+?)\)" % names,
230            r"%s has not played any rated games\." % names,
231            "rating +RD +win +loss +draw +total +best",
232            "(?P<gametype>%s) +(?P<ratings>.+)" % types,
233            "Email *: (?P<email>.+)", "Sanctions *: (?P<sanctions>.+)",
234            "Total time online: (?P<tto>.+)",
235            r"% of life online:  [\d\.]+  \(since (?P<created>.+?)\)",
236            r"Timeseal [ \d] : (?P<timeseal>Off|On)",
237            "Admin Level: (?P<adminlevel>.+)",
238            r"(?P<noteno>\d+): *(?P<note>.*)", "$")
239
240        self.connection.expect_fromplus(self.onFinger, "Finger of %s:" % names,
241                                        "$|".join(fingerLines))
242
243        self.connection.client.run_command("iset nowrap 1")
244
245        # We don't use this. Rather we use BoardManagers "gameEnded", after
246        # which we do a refinger. This is to ensure not only rating, but also
247        # wins/looses/draws are updated
248        # self.connection.expect(self.onRatingAdjust,
249        #        "%s rating adjustment: (\d+) --> (\d+)" % types
250        # Notice if you uncomment this: The expression has to be compiled with
251        # re.IGNORECASE, or the first letter of 'type' must be capital
252
253    def parseDate(self, date):
254        # Tue Mar 11, 10:56 PDT 2008
255        return date
256
257    def parseShortDate(self, date):
258        # 24-Oct-2007
259        return date
260
261    def parseTime(self, time):
262        # 3 days, 2 hrs, 53 mins
263        return time
264
265    def onFinger(self, matchlist):
266        finger = FingerObject()
267        name = matchlist[0].groups()[0]
268        finger.setName(name)
269        if matchlist[0].groups()[1]:
270            titles = re.findall(titleslist, matchlist[0].groups()[1])
271            finger.setTitles(titles)
272        for match in matchlist[1:]:
273            if not match.group():
274                continue
275            groupdict = match.groupdict()
276            if groupdict["never"] is not None:
277                finger.setStatus(IC_STATUS_OFFLINE)
278            elif groupdict["last"] is not None:
279                finger.setStatus(IC_STATUS_OFFLINE)
280                finger.setLastSeen(self.parseDate(groupdict["last"]))
281            elif groupdict["uptime"] is not None:
282                finger.setStatus(IC_STATUS_ACTIVE)
283                finger.setUpTime(self.parseTime(groupdict["uptime"]))
284                finger.setIdleTime(self.parseTime(groupdict["idletime"]))
285            elif groupdict["silence"] is not None:
286                finger.setSilence(True)
287            elif groupdict["gameno"] is not None:
288                finger.setStatus(IC_STATUS_PLAYING)
289                finger.setGameno(groupdict["gameno"])
290                if groupdict["p1"].lower() == self.connection.getUsername(
291                ).lower():
292                    finger.setColor(WHITE)
293                    finger.setOpponent(groupdict["p2"])
294                else:
295                    finger.setColor(BLACK)
296                    finger.setOpponent(groupdict["p1"])
297            elif groupdict["busymessage"] is not None:
298                finger.setStatus(IC_STATUS_BUSY)
299                finger.setBusyMessage(groupdict["busymessage"])
300            elif groupdict["gametype"] is not None:
301                gametype = GAME_TYPES_BY_FICS_NAME[groupdict["gametype"].lower()]
302                ratings = groupdict["ratings"].split()
303                finger.setRating(gametype.rating_type, ratings)
304            elif groupdict["email"] is not None:
305                finger.setEmail(groupdict["email"])
306            elif groupdict["sanctions"] is not None:
307                finger.setSanctions(groupdict["sanctions"])
308            elif groupdict["tto"] is not None:
309                finger.setTotalTimeOnline(self.parseTime(groupdict["tto"]))
310            elif groupdict["created"] is not None:
311                finger.setCreated(self.parseDate(groupdict["created"]))
312            elif groupdict["timeseal"] is not None:
313                finger.setTimeseal(groupdict["timeseal"] == "On")
314            elif groupdict["adminlevel"] is not None:
315                finger.setAdminLevel(groupdict["adminlevel"])
316            elif groupdict["noteno"] is not None:
317                finger.setNote(int(groupdict["noteno"]) - 1, groupdict["note"])
318            else:
319                log.debug("Ignored fingerline: %s" % repr(match.group()))
320
321        self.emit("fingeringFinished", finger)
322
323    onFinger.BLKCMD = BLKCMD_FINGER
324
325    def onRatingAdjust(self, match):
326        # Notice: This is only recived for us, not for other persons we finger
327        rating_type, old, new = match.groups()
328        self.emit("ratingAdjusted", rating_type, new)
329
330    def finger(self, user):
331        self.connection.client.run_command("finger %s" % user)
332
333    def setFingerNote(self, note, message):
334        assert 1 <= note <= 10
335        self.connection.client.run_command("set %d %s" % (note, message))
336
337    def setBusyMessage(self, message):
338        """ Like set busy is really busy right now. """
339        self.connection.client.run_command("set busy %s" % message)
340