Advertisement
Guest User

Untitled

a guest
May 4th, 2017
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 61.60 KB | None | 0 0
  1. ################################################################
  2. # File: ch.py
  3. # Title: Chatango Library
  4. # Original Author: Lumirayz/Lumz <lumirayz@gmail.com>
  5. # Version: 1.3.8
  6. # Description:
  7. #  An event-based library for connecting to one or multiple Chatango rooms, has
  8. #  support for several things including: messaging, message font,
  9. #  name color, deleting, banning, recent history, 2 userlist modes,
  10. #  flagging, avoiding flood bans, detecting flags.
  11. # Contact Info:
  12. #  Any question, comment, or suggestion should be directed to the current
  13. #  maintainers and contributors, located at:
  14. #   https://github.com/Nullspeaker/ch.py
  15. #  Where a more satisfactory response to all bug reports (which can be made on the
  16. #  issues page) and other statements can be garnered. For things not specific or
  17. #  in direct reference to this library, 'ch.py', a direct response can be filed
  18. #  to the individual persons listed above as 'Current Maintainers and Contributors.'
  19. ################################################################
  20.  
  21. # Chlib fixed up - sort of
  22.  
  23. ###########
  24. # Imports #
  25. ###########
  26.  
  27. import socket
  28. import threading
  29. import time
  30. import random
  31. import re
  32. import sys
  33. import select
  34.  
  35. #########
  36. # Debug #
  37. #########
  38.  
  39. debug = False
  40.  
  41. ############
  42. # Python 2 #
  43. ############
  44.  
  45. if sys.version_info[0] < 3:
  46.   class urllib:
  47.     parse = __import__("urllib")
  48.     request = __import__("urllib2")
  49.   input = raw_input
  50.   import codecs
  51.   import Queue as queue
  52. else:
  53.   import queue
  54.   import urllib.request
  55.   import urllib.parse
  56.  
  57. #############
  58. # Constants #
  59. #############
  60.  
  61. Userlist_Recent = 0
  62. Userlist_All = 1
  63.  
  64. BigMessage_Multiple = 0
  65. BigMessage_Cut = 1
  66.  
  67. Number_of_Threads = 1
  68.  
  69. ################
  70. # Struct class #
  71. ################
  72.  
  73. class Struct:
  74.   def __init__(self, **entries):
  75.     self.__dict__.update(entries)
  76.  
  77. ##############
  78. # Tag server #
  79. ##############
  80.  
  81. specials = {'mitvcanal': 56, 'animeultimacom': 34, 'cricket365live': 21, 'pokemonepisodeorg': 22, 'animelinkz': 20, 'sport24lt': 56, 'narutowire': 10, 'watchanimeonn': 22, 'cricvid-hitcric-': 51, 'narutochatt': 70, 'leeplarp': 27, 'stream2watch3': 56, 'ttvsports': 56, 'ver-anime': 8, 'vipstand': 21, 'eafangames': 56, 'soccerjumbo': 21, 'myfoxdfw': 67, 'kiiiikiii': 21, 'de-livechat': 5, 'rgsmotrisport': 51, 'dbzepisodeorg': 10, 'watch-dragonball': 8, 'peliculas-flv': 69, 'tvanimefreak': 54, 'tvtvanimefreak': 54}
  82. tsweights = [['5', 75], ['6', 75], ['7', 75], ['8', 75], ['16', 75], ['17', 75], ['18', 75], ['9', 95], ['11', 95], ['12', 95], ['13', 95], ['14', 95], ['15', 95], ['19', 110], ['23', 110], ['24', 110], ['25', 110], ['26', 110], ['28', 104], ['29', 104], ['30', 104], ['31', 104], ['32', 104], ['33', 104], ['35', 101], ['36', 101], ['37', 101], ['38', 101], ['39', 101], ['40', 101], ['41', 101], ['42', 101], ['43', 101], ['44', 101], ['45', 101], ['46', 101], ['47', 101], ['48', 101], ['49', 101], ['50', 101], ['52', 110], ['53', 110], ['55', 110], ['57', 110], ['58', 110], ['59', 110], ['60', 110], ['61', 110], ['62', 110], ['63', 110], ['64', 110], ['65', 110], ['66', 110], ['68', 95], ['71', 116], ['72', 116], ['73', 116], ['74', 116], ['75', 116], ['76', 116], ['77', 116], ['78', 116], ['79', 116], ['80', 116], ['81', 116], ['82', 116], ['83', 116], ['84', 116]]
  83.  
  84. def getServer(group):
  85.   """
  86.  Get the server host for a certain room.
  87.  
  88.  @type group: str
  89.  @param group: room name
  90.  
  91.  @rtype: str
  92.  @return: the server's hostname
  93.  """
  94.   try:
  95.     sn = specials[group]
  96.   except KeyError:
  97.     group = group.replace("_", "q")
  98.     group = group.replace("-", "q")
  99.     fnv = float(int(group[0:min(5, len(group))], 36))
  100.     lnv = group[6: (6 + min(3, len(group) - 5))]
  101.     if(lnv):
  102.       lnv = float(int(lnv, 36))
  103.       lnv = max(lnv,1000)
  104.     else:
  105.       lnv = 1000
  106.     num = (fnv % lnv) / lnv
  107.     maxnum = sum(map(lambda x: x[1], tsweights))
  108.     cumfreq = 0
  109.     sn = 0
  110.     for wgt in tsweights:
  111.       cumfreq += float(wgt[1]) / maxnum
  112.       if(num <= cumfreq):
  113.         sn = int(wgt[0])
  114.         break
  115.   return "s"+str(sn)+".chatango.com"
  116.  
  117. ###########
  118. # User ID #
  119. ###########
  120.  
  121. def _genUid():
  122.   """
  123.  generate a uid
  124.  """
  125.   return str(random.randrange(10 ** 15, 10 ** 16))
  126.  
  127. ###########
  128. # Message #
  129. ###########
  130.  
  131. def _clean_message(msg):
  132.   """
  133.  Clean a message and return the message, n tag and f tag.
  134.  
  135.  @type msg: str
  136.  @param msg: the message
  137.  
  138.  @rtype: str, str, str
  139.  @returns: cleaned message, n tag contents, f tag contents
  140.  """
  141.   n = re.search("<n(.*?)/>", msg)
  142.   if n: n = n.group(1)
  143.   f = re.search("<f(.*?)>", msg)
  144.   if f: f = f.group(1)
  145.   msg = re.sub("<n.*?/>", "", msg)
  146.   msg = re.sub("<f.*?>", "", msg)
  147.   msg = _strip_html(msg)
  148.   msg = msg.replace("&lt;", "<")
  149.   msg = msg.replace("&gt;", ">")
  150.   msg = msg.replace("&quot;", "\"")
  151.   msg = msg.replace("&apos;", "'")
  152.   msg = msg.replace("&amp;", "&")
  153.   return msg, n, f
  154.  
  155. def _strip_html(msg):
  156.   """Strip HTML."""
  157.   li = msg.split("<")
  158.   if len(li) == 1:
  159.     return li[0]
  160.   else:
  161.     ret = list()
  162.     for data in li:
  163.       data = data.split(">", 1)
  164.       if len(data) == 1:
  165.         ret.append(data[0])
  166.       elif len(data) == 2:
  167.         ret.append(data[1])
  168.     return "".join(ret)
  169.  
  170. def _parseNameColor(n):
  171.   """This just returns its argument, should return the name color."""
  172.   #probably is already the name
  173.   return n
  174.  
  175. def _parseFont(f):
  176.   """Parses the contents of a f tag and returns color, face and size."""
  177.   #' xSZCOL="FONT"'
  178.   try: #TODO: remove quick hack
  179.     sizecolor, fontface = f.split("=", 1)
  180.     sizecolor = sizecolor.strip()
  181.     size = int(sizecolor[1:3])
  182.     col = sizecolor[3:6]
  183.     if col == "": col = None
  184.     face = f.split("\"", 2)[1]
  185.     return col, face, size
  186.   except:
  187.     return None, None, None
  188.  
  189. ###########
  190. # Anon id #
  191. ###########
  192.  
  193. def _getAnonId(n, ssid):
  194.   """Gets an anon's id."""
  195.   if n == None: n = "5504"
  196.   try:
  197.     return "".join(list(
  198.       map(lambda x: str(x[0] + x[1])[-1], list(zip(
  199.         list(map(lambda x: int(x), n)),
  200.         list(map(lambda x: int(x), ssid[4:]))
  201.       )))
  202.     ))
  203.   except ValueError:
  204.     return "NNNN"
  205.  
  206. #################
  207. # Anon PM class #
  208. #################
  209.  
  210. class _ANON_PM_OBJECT:
  211.   """Manages connection with Chatango anon PM."""
  212.   def __init__(self, mgr, name):
  213.     self._connected = False
  214.     self._mgr = mgr
  215.     self._wlock = False
  216.     self._firstCommand = True
  217.     self._wbuf = b""
  218.     self._wlockbuf = b""
  219.     self._rbuf = b""
  220.     self._pingTask = None
  221.     self._name = name
  222.  
  223.   def _auth(self):
  224.     self._sendCommand("mhs","mini","unknown","%s" % (self._name))
  225.     self._setWriteLock(True)
  226.     return True
  227.  
  228.   def disconnect(self):
  229.     """Disconnect the bot from PM"""
  230.     self._disconnect()
  231.     self._callEvent("onAnonPMDisconnect", User(self._name))
  232.  
  233.   def _disconnect(self):
  234.     self._connected = False
  235.     self._sock.close()
  236.     self._sock = None
  237.  
  238.   def ping(self):
  239.     """send a ping"""
  240.     self._sendCommand("")
  241.     self._callEvent("onPMPing")
  242.  
  243.   def message(self, user, msg):
  244.     """send a pm to a user"""
  245.     if msg!=None:
  246.       self._sendCommand("msg", user.name, msg)
  247.  
  248.   ####
  249.   # Feed
  250.   ####
  251.   def _feed(self, data):
  252.     """
  253.    Feed data to the connection.
  254.  
  255.    @type data: bytes
  256.    @param data: data to be fed
  257.    """
  258.     self._rbuf += data
  259.     while self._rbuf.find(b"\x00") != -1:
  260.       data = self._rbuf.split(b"\x00")
  261.       for food in data[:-1]:
  262.         self._process(food.decode(errors="replace").rstrip("\r\n"))
  263.       self._rbuf = data[-1]
  264.  
  265.   def _process(self, data):
  266.     """
  267.    Process a command string.
  268.  
  269.    @type data: str
  270.    @param data: the command string
  271.    """
  272.     self._callEvent("onRaw", data)
  273.     data = data.split(":")
  274.     cmd, args = data[0], data[1:]
  275.     func = "_rcmd_" + cmd
  276.     if hasattr(self, func):
  277.       getattr(self, func)(args)
  278.     else:
  279.       if debug:
  280.         print("unknown data: "+str(data))
  281.  
  282.   def _getManager(self): return self._mgr
  283.  
  284.   mgr = property(_getManager)
  285.  
  286.   ####
  287.   # Received Commands
  288.   ####
  289.  
  290.   def _rcmd_mhs(self, args):
  291.     """
  292.    note to future maintainers
  293.    
  294.    args[1] is ether "online" or "offline"
  295.    """
  296.     self._connected = True
  297.     self._setWriteLock(False)
  298.  
  299.   def _rcmd_msg(self, args):
  300.     user = User(args[0])
  301.     body = _strip_html(":".join(args[5:]))
  302.     self._callEvent("onPMMessage", user, body)
  303.  
  304.   ####
  305.   # Util
  306.   ####
  307.   def _callEvent(self, evt, *args, **kw):
  308.     getattr(self.mgr, evt)(self, *args, **kw)
  309.     self.mgr.onEventCalled(self, evt, *args, **kw)
  310.  
  311.   def _write(self, data):
  312.     if self._wlock:
  313.       self._wlockbuf += data
  314.     else:
  315.       self.mgr._write(self, data)
  316.  
  317.   def _setWriteLock(self, lock):
  318.     self._wlock = lock
  319.     if self._wlock == False:
  320.       self._write(self._wlockbuf)
  321.       self._wlockbuf = b""
  322.  
  323.   def _sendCommand(self, *args):
  324.     """
  325.    Send a command.
  326.  
  327.    @type args: [str, str, ...]
  328.    @param args: command and list of arguments
  329.    """
  330.     if self._firstCommand:
  331.       terminator = b"\x00"
  332.       self._firstCommand = False
  333.     else:
  334.       terminator = b"\r\n\x00"
  335.     self._write(":".join(args).encode() + terminator)
  336.  
  337. class ANON_PM:
  338.   """Comparable wrapper for anon Chatango PM"""
  339.   ####
  340.   # Init
  341.   ####
  342.   def __init__(self, mgr):
  343.     self._mgr = mgr
  344.     self._wlock = False
  345.     self._firstCommand = True
  346.     self._persons = dict()
  347.     self._wlockbuf = b""
  348.     self._pingTask = None
  349.  
  350.   ####
  351.   # Connections
  352.   ####
  353.   def _connect(self,name):
  354.     self._persons[name] = _ANON_PM_OBJECT(self._mgr,name)
  355.     sock = socket.socket()
  356.     sock.connect((self._mgr._anonPMHost, self._mgr._PMPort))
  357.     sock.setblocking(False)
  358.     self._persons[name]._sock = sock
  359.     if not self._persons[name]._auth(): return
  360.     self._persons[name]._pingTask = self._mgr.setInterval(self._mgr._pingDelay, self._persons[name].ping)
  361.     self._persons[name]._connected = True
  362.  
  363.   def message(self, user, msg):
  364.     """send a pm to a user"""
  365.     if not user.name in self._persons:
  366.       self._connect(user.name)
  367.     self._persons[user.name].message(user,msg)
  368.  
  369.   def getConnections(self):
  370.     return list(self._persons.values())
  371.  
  372. ############
  373. # PM class #
  374. ############
  375.  
  376. class PM:
  377.   """Manages a connection with Chatango PM."""
  378.   ####
  379.   # Init
  380.   ####
  381.   def __init__(self, mgr):
  382.     self._auth_re = re.compile(r"auth\.chatango\.com ?= ?([^;]*)", re.IGNORECASE)
  383.     self._connected = False
  384.     self._mgr = mgr
  385.     self._auid = None
  386.     self._blocklist = set()
  387.     self._contacts = set()
  388.     self._status = dict()
  389.     self._wlock = False
  390.     self._firstCommand = True
  391.     self._wbuf = b""
  392.     self._wlockbuf = b""
  393.     self._rbuf = b""
  394.     self._pingTask = None
  395.     self._connect()
  396.  
  397.   ####
  398.   # Connections
  399.   ####
  400.   def _connect(self):
  401.     self._wbuf = b""
  402.     self._sock = socket.socket()
  403.     self._sock.connect((self._mgr._PMHost, self._mgr._PMPort))
  404.     self._sock.setblocking(False)
  405.     self._firstCommand = True
  406.     if not self._auth(): return
  407.     self._pingTask = self.mgr.setInterval(self._mgr._pingDelay, self.ping)
  408.     self._connected = True
  409.  
  410.  
  411.   def _getAuth(self, name, password):
  412.     """
  413.    Request an auid using name and password.
  414.  
  415.    @type name: str
  416.    @param name: name
  417.    @type password: str
  418.    @param password: password
  419.  
  420.    @rtype: str
  421.    @return: auid
  422.    """
  423.     data = urllib.parse.urlencode({
  424.       "user_id": name,
  425.       "password": password,
  426.       "storecookie": "on",
  427.       "checkerrors": "yes"
  428.     }).encode()
  429.     try:
  430.       resp = urllib.request.urlopen("http://chatango.com/login", data)
  431.       headers = resp.headers
  432.     except Exception:
  433.       return None
  434.     for header, value in headers.items():
  435.       if header.lower() == "set-cookie":
  436.         m = self._auth_re.search(value)
  437.         if m:
  438.           auth = m.group(1)
  439.           if auth == "":
  440.             return None
  441.           return auth
  442.     return None
  443.  
  444.   def _auth(self):
  445.     self._auid = self._getAuth(self._mgr.name, self._mgr.password)
  446.     if self._auid == None:
  447.       self._sock.close()
  448.       self._callEvent("onLoginFail")
  449.       self._sock = None
  450.       return False
  451.     self._sendCommand("tlogin", self._auid, "2")
  452.     self._setWriteLock(True)
  453.     return True
  454.  
  455.   def disconnect(self):
  456.     """Disconnect the bot from PM"""
  457.     self._disconnect()
  458.     self._callEvent("onPMDisconnect")
  459.  
  460.   def _disconnect(self):
  461.     self._connected = False
  462.     self._sock.close()
  463.     self._sock = None
  464.  
  465.   ####
  466.   # Feed
  467.   ####
  468.   def _feed(self, data):
  469.     """
  470.    Feed data to the connection.
  471.  
  472.    @type data: bytes
  473.    @param data: data to be fed
  474.    """
  475.     self._rbuf += data
  476.     while self._rbuf.find(b"\x00") != -1:
  477.       data = self._rbuf.split(b"\x00")
  478.       for food in data[:-1]:
  479.         self._process(food.decode(errors="replace").rstrip("\r\n"))
  480.       self._rbuf = data[-1]
  481.  
  482.   def _process(self, data):
  483.     """
  484.    Process a command string.
  485.  
  486.    @type data: str
  487.    @param data: the command string
  488.    """
  489.     self._callEvent("onRaw", data)
  490.     data = data.split(":")
  491.     cmd, args = data[0], data[1:]
  492.     func = "_rcmd_" + cmd
  493.     if hasattr(self, func):
  494.       getattr(self, func)(args)
  495.     else:
  496.       if debug:
  497.         print("unknown data: "+str(data))
  498.  
  499.   ####
  500.   # Properties
  501.   ####
  502.   def _getManager(self): return self._mgr
  503.   def _getContacts(self): return self._contacts
  504.   def _getBlocklist(self): return self._blocklist
  505.  
  506.   mgr = property(_getManager)
  507.   contacts = property(_getContacts)
  508.   blocklist = property(_getBlocklist)
  509.  
  510.   ####
  511.   # Received Commands
  512.   ####
  513.   def _rcmd_OK(self, args):
  514.     self._setWriteLock(False)
  515.     self._sendCommand("wl")
  516.     self._sendCommand("getblock")
  517.     self._callEvent("onPMConnect")
  518.  
  519.   def _rcmd_wl(self, args):
  520.     self._contacts = set()
  521.     for i in range(len(args) // 4):
  522.       name, last_on, is_on, idle = args[i * 4: i * 4 + 4]
  523.       user = User(name)
  524.       if last_on=="None":pass#in case chatango gives a "None" as data argument
  525.       elif not is_on == "on": self._status[user] = [int(last_on), False, 0]
  526.       elif idle == '0': self._status[user] = [int(last_on), True, 0]
  527.       else: self._status[user] = [int(last_on), True, time.time() - int(idle) * 60]
  528.       self._contacts.add(user)
  529.     self._callEvent("onPMContactlistReceive")
  530.  
  531.   def _rcmd_block_list(self, args):
  532.     self._blocklist = set()
  533.     for name in args:
  534.       if name == "": continue
  535.       self._blocklist.add(User(name))
  536.  
  537.   def _rcmd_track(self, args):
  538.     user = User(args[0])
  539.     if user in self._status:
  540.       last_on = self._status[user][0]
  541.     else:
  542.       last_on = 0
  543.     if args[1] == '0':
  544.       idle = 0
  545.     else:
  546.       idle = time.time() - int(args[1]) * 60
  547.     if args[2] == "online":
  548.       is_on = True
  549.     else:
  550.       is_on = False
  551.     self._status[user] = [last_on, is_on, idle]
  552.  
  553.   def _rcmd_DENIED(self, args):
  554.     self._disconnect()
  555.     self._callEvent("onLoginFail")
  556.  
  557.   def _rcmd_msg(self, args):
  558.     user = User(args[0])
  559.     body = _strip_html(":".join(args[5:]))
  560.     self._callEvent("onPMMessage", user, body)
  561.  
  562.   def _rcmd_msgoff(self, args):
  563.     user = User(args[0])
  564.     body = _strip_html(":".join(args[5:]))
  565.     self._callEvent("onPMOfflineMessage", user, body)
  566.  
  567.   def _rcmd_wlonline(self, args):
  568.     user = User(args[0])
  569.     last_on = float(args[1])
  570.     self._status[user] = [last_on,True,last_on]
  571.     self._callEvent("onPMContactOnline", user)
  572.  
  573.   def _rcmd_wloffline(self, args):
  574.     user = User(args[0])
  575.     last_on = float(args[1])
  576.     self._status[user] = [last_on,False,0]
  577.     self._callEvent("onPMContactOffline", user)
  578.  
  579.   def _rcmd_kickingoff(self, args):
  580.     self.disconnect()
  581.  
  582.   def _rcmd_toofast(self, args):
  583.     self.disconnect()
  584.  
  585.   def _rcmd_unblocked(self, user):
  586.     """call when successfully unblocked"""
  587.     if user in self._blocklist:
  588.       self._blocklist.remove(user)
  589.       self._callEvent("onPMUnblock", user)
  590.  
  591.  
  592.   ####
  593.   # Commands
  594.   ####
  595.   def ping(self):
  596.     """send a ping"""
  597.     self._sendCommand("")
  598.     self._callEvent("onPMPing")
  599.  
  600.   def message(self, user, msg):
  601.     """send a pm to a user"""
  602.     if msg!=None:
  603.       self._sendCommand("msg", user.name, msg)
  604.  
  605.   def addContact(self, user):
  606.     """add contact"""
  607.     if user not in self._contacts:
  608.       self._sendCommand("wladd", user.name)
  609.       self._contacts.add(user)
  610.       self._callEvent("onPMContactAdd", user)
  611.  
  612.   def removeContact(self, user):
  613.     """remove contact"""
  614.     if user in self._contacts:
  615.       self._sendCommand("wldelete", user.name)
  616.       self._contacts.remove(user)
  617.       self._callEvent("onPMContactRemove", user)
  618.  
  619.   def block(self, user):
  620.     """block a person"""
  621.     if user not in self._blocklist:
  622.       self._sendCommand("block", user.name, user.name, "S")
  623.       self._blocklist.add(user)
  624.       self._callEvent("onPMBlock", user)
  625.  
  626.   def unblock(self, user):
  627.     """unblock a person"""
  628.     if user in self._blocklist:
  629.       self._sendCommand("unblock", user.name)
  630.  
  631.   def track(self, user):
  632.     """get and store status of person for future use"""
  633.     self._sendCommand("track", user.name)
  634.  
  635.   def checkOnline(self, user):
  636.     """return True if online, False if offline, None if unknown"""
  637.     if user in self._status:
  638.       return self._status[user][1]
  639.     else:
  640.       return None
  641.  
  642.   def getIdle(self, user):
  643.     """return last active time, time.time() if isn't idle, 0 if offline, None if unknown"""
  644.     if not user in self._status: return None
  645.     if not self._status[user][1]: return 0
  646.     if not self._status[user][2]: return time.time()
  647.     else: return self._status[user][2]
  648.  
  649.   ####
  650.   # Util
  651.   ####
  652.   def _callEvent(self, evt, *args, **kw):
  653.     getattr(self.mgr, evt)(self, *args, **kw)
  654.     self.mgr.onEventCalled(self, evt, *args, **kw)
  655.  
  656.   def _write(self, data):
  657.     if self._wlock:
  658.       self._wlockbuf += data
  659.     else:
  660.       self.mgr._write(self, data)
  661.  
  662.   def _setWriteLock(self, lock):
  663.     self._wlock = lock
  664.     if self._wlock == False:
  665.       self._write(self._wlockbuf)
  666.       self._wlockbuf = b""
  667.  
  668.   def _sendCommand(self, *args):
  669.     """
  670.    Send a command.
  671.  
  672.    @type args: [str, str, ...]
  673.    @param args: command and list of arguments
  674.    """
  675.     if self._firstCommand:
  676.       terminator = b"\x00"
  677.       self._firstCommand = False
  678.     else:
  679.       terminator = b"\r\n\x00"
  680.     self._write(":".join(args).encode() + terminator)
  681.  
  682.   def getConnections(self):
  683.     return [self]
  684.  
  685. ##############
  686. # Room class #
  687. ##############
  688.  
  689. class Room:
  690.   """Manages a connection with a Chatango room."""
  691.   ####
  692.   # Init
  693.   ####
  694.   def __init__(self, room, uid = None, server = None, port = None, mgr = None):
  695.     """init, don't overwrite"""
  696.     # Basic stuff
  697.     self._name = room
  698.     self._server = server or getServer(room)
  699.     self._port = port or 443
  700.     self._mgr = mgr
  701.  
  702.     # Under the hood
  703.     self._connected = False
  704.     self._reconnecting = False
  705.     self._uid = uid or _genUid()
  706.     self._rbuf = b""
  707.     self._wbuf = b""
  708.     self._wlockbuf = b""
  709.     self._owner = None
  710.     self._mods = set()
  711.     self._mqueue = dict()
  712.     self._history = list()
  713.     self._userlist = list()
  714.     self._firstCommand = True
  715.     self._connectAmmount = 0
  716.     self._premium = False
  717.     self._userCount = 0
  718.     self._pingTask = None
  719.     self._botname = None
  720.     self._currentname = None
  721.     self._users = dict()
  722.     self._msgs = dict()
  723.     self._wlock = False
  724.     self._silent = False
  725.     self._banlist = dict()
  726.     self._unbanlist = dict()
  727.  
  728.     # Inited vars
  729.     if self._mgr: self._connect()
  730.  
  731.   ####
  732.   # Connect/disconnect
  733.   ####
  734.   def _connect(self):
  735.     """Connect to the server."""
  736.     self._sock = socket.socket()
  737.     self._sock.connect((self._server, self._port))
  738.     self._sock.setblocking(False)
  739.     self._firstCommand = True
  740.     self._wbuf = b""
  741.     self._auth()
  742.     self._pingTask = self.mgr.setInterval(self.mgr._pingDelay, self.ping)
  743.     if not self._reconnecting: self.connected = True
  744.  
  745.   def reconnect(self):
  746.     """Reconnect."""
  747.     self._reconnect()
  748.  
  749.   def _reconnect(self):
  750.     """Reconnect."""
  751.     self._reconnecting = True
  752.     if self.connected:
  753.       self._disconnect()
  754.     self._uid = _genUid()
  755.     self._connect()
  756.     self._reconnecting = False
  757.  
  758.   def disconnect(self):
  759.     """Disconnect."""
  760.     self._disconnect()
  761.     self._callEvent("onDisconnect")
  762.  
  763.   def _disconnect(self):
  764.     """Disconnect from the server."""
  765.     if not self._reconnecting: self.connected = False
  766.     for user in self._userlist:
  767.       user.clearSessionIds(self)
  768.     self._userlist = list()
  769.     self._pingTask.cancel()
  770.     self._sock.close()
  771.     if not self._reconnecting: del self.mgr._rooms[self.name]
  772.  
  773.   def _auth(self):
  774.     """Authenticate."""
  775.     # login as name with password
  776.     if self.mgr.name and self.mgr.password:
  777.       self._sendCommand("bauth", self.name, self._uid, self.mgr.name, self.mgr.password)
  778.       self._currentname = self.mgr.name
  779.     # login as anon
  780.     else:
  781.       self._sendCommand("bauth", self.name)
  782.  
  783.     self._setWriteLock(True)
  784.  
  785.   ####
  786.   # Properties
  787.   ####
  788.   def _getName(self): return self._name
  789.   def _getBotName(self):
  790.     if self.mgr.name and self.mgr.password:
  791.       return self.mgr.name
  792.     elif self.mgr.name and self.mgr.password == None:
  793.       return "#" + self.mgr.name
  794.     elif self.mgr.name == None:
  795.       return self._botname
  796.   def _getCurrentname(self): return self._currentname
  797.   def _getManager(self): return self._mgr
  798.   def _getUserlist(self, mode = None, unique = None, memory = None):
  799.     ul = None
  800.     if mode == None: mode = self.mgr._userlistMode
  801.     if unique == None: unique = self.mgr._userlistUnique
  802.     if memory == None: memory = self.mgr._userlistMemory
  803.     if mode == Userlist_Recent:
  804.       ul = map(lambda x: x.user, self._history[-memory:])
  805.     elif mode == Userlist_All:
  806.       ul = self._userlist
  807.     if unique:
  808.       return list(set(ul))
  809.     else:
  810.       return ul
  811.   def _getUserNames(self):
  812.     ul = self.userlist
  813.     return list(map(lambda x: x.name, ul))
  814.   def _getUser(self): return self.mgr.user
  815.   def _getOwner(self): return self._owner
  816.   def _getOwnerName(self): return self._owner.name
  817.   def _getMods(self):
  818.     newset = set()
  819.     for mod in self._mods:
  820.       newset.add(mod)
  821.     return newset
  822.   def _getModNames(self):
  823.     mods = self._getMods()
  824.     return [x.name for x in mods]
  825.   def _getUserCount(self): return self._userCount
  826.   def _getSilent(self): return self._silent
  827.   def _setSilent(self, val): self._silent = val
  828.   def _getBanlist(self): return list(self._banlist.keys())
  829.   def _getUnBanlist(self): return [[record["target"], record["src"]] for record in self._unbanlist.values()]
  830.  
  831.   name = property(_getName)
  832.   botname = property(_getBotName)
  833.   currentname = property(_getCurrentname)
  834.   mgr = property(_getManager)
  835.   userlist = property(_getUserlist)
  836.   usernames = property(_getUserNames)
  837.   user = property(_getUser)
  838.   owner = property(_getOwner)
  839.   ownername = property(_getOwnerName)
  840.   mods = property(_getMods)
  841.   modnames = property(_getModNames)
  842.   usercount = property(_getUserCount)
  843.   silent = property(_getSilent, _setSilent)
  844.   banlist = property(_getBanlist)
  845.   unbanlist = property(_getUnBanlist)
  846.  
  847.   ####
  848.   # Feed/process
  849.   ####
  850.   def _feed(self, data):
  851.     """
  852.    Feed data to the connection.
  853.  
  854.    @type data: bytes
  855.    @param data: data to be fed
  856.    """
  857.     self._rbuf += data
  858.     while self._rbuf.find(b"\x00") != -1:
  859.       data = self._rbuf.split(b"\x00")
  860.       for food in data[:-1]:
  861.         self._process(food.decode(errors="replace").rstrip("\r\n"))
  862.       self._rbuf = data[-1]
  863.  
  864.   def _process(self, data):
  865.     """
  866.    Process a command string.
  867.    
  868.    @type data: str
  869.    @param data: the command string
  870.    """
  871.     self._callEvent("onRaw", data)
  872.     data = data.split(":")
  873.     cmd, args = data[0], data[1:]
  874.     func = "_rcmd_" + cmd
  875.     if hasattr(self, func):
  876.       getattr(self, func)(args)
  877.     else:
  878.       if debug:
  879.         print("unknown data: "+str(data))
  880.  
  881.   ####
  882.   # Received Commands
  883.   ####
  884.   def _rcmd_ok(self, args):
  885.     # if no name, join room as anon and no password
  886.     if args[2] == "N" and self.mgr.password == None and self.mgr.name == None:
  887.       n = args[4].rsplit('.', 1)[0]
  888.       n = n[-4:]
  889.       aid = args[1][0:8]
  890.       pid = "!anon" + _getAnonId(n, aid)
  891.       self._botname = pid
  892.       self._currentname = pid
  893.       self.user._nameColor = n
  894.     # if got name, join room as name and no password
  895.     elif args[2] == "N" and self.mgr.password == None:
  896.       self._sendCommand("blogin", self.mgr.name)
  897.       self._currentname = self.mgr.name
  898.     # if got password but fail to login
  899.     elif args[2] != "M": #unsuccesful login
  900.       self._callEvent("onLoginFail")
  901.       self.disconnect()
  902.     self._owner = User(args[0])
  903.     self._uid = args[1]
  904.     self._aid = args[1][4:8]
  905.     self._mods = set(map(lambda x: User(x.split(",")[0]), args[6].split(";")))
  906.     self._i_log = list()
  907.  
  908.   def _rcmd_denied(self, args):
  909.     self._disconnect()
  910.     self._callEvent("onConnectFail")
  911.  
  912.   def _rcmd_inited(self, args):
  913.     self._sendCommand("g_participants", "start")
  914.     self._sendCommand("getpremium", "1")
  915.     self.requestBanlist()
  916.     self.requestUnBanlist()
  917.     if self._connectAmmount == 0:
  918.       self._callEvent("onConnect")
  919.       for msg in reversed(self._i_log):
  920.         user = msg.user
  921.         self._callEvent("onHistoryMessage", user, msg)
  922.         self._addHistory(msg)
  923.       del self._i_log
  924.     else:
  925.       self._callEvent("onReconnect")
  926.     self._connectAmmount += 1
  927.     self._setWriteLock(False)
  928.  
  929.   def _rcmd_premium(self, args):
  930.     if float(args[1]) > time.time():
  931.       self._premium = True
  932.       if self.user._mbg: self.setBgMode(1)
  933.       if self.user._mrec: self.setRecordingMode(1)
  934.     else:
  935.       self._premium = False
  936.  
  937.   def _rcmd_mods(self, args):
  938.     modnames = args
  939.     mods = set(map(lambda x: User(x.split(",")[0]), modnames))
  940.     premods = self._mods
  941.     for user in mods - premods: #modded
  942.       self._mods.add(user)
  943.       self._callEvent("onModAdd", user)
  944.     for user in premods - mods: #demodded
  945.       self._mods.remove(user)
  946.       self._callEvent("onModRemove", user)
  947.     self._callEvent("onModChange")
  948.  
  949.   def _rcmd_b(self, args):
  950.     mtime = float(args[0])
  951.     puid = args[3]
  952.     ip = args[6]
  953.     name = args[1]
  954.     rawmsg = ":".join(args[9:])
  955.     msg, n, f = _clean_message(rawmsg)
  956.     if name == "":
  957.       nameColor = None
  958.       name = "#" + args[2]
  959.       if name == "#":
  960.         name = "!anon" + _getAnonId(n, puid)
  961.     else:
  962.       if n: nameColor = _parseNameColor(n)
  963.       else: nameColor = None
  964.     i = args[5]
  965.     unid = args[4]
  966.     user = User(name)
  967.     #Create an anonymous message and queue it because msgid is unknown.
  968.     if f: fontColor, fontFace, fontSize = _parseFont(f)
  969.     else: fontColor, fontFace, fontSize = None, None, None
  970.     msg = Message(
  971.       time = mtime,
  972.       user = user,
  973.       body = msg,
  974.       raw = rawmsg,
  975.       ip = ip,
  976.       nameColor = nameColor,
  977.       fontColor = fontColor,
  978.       fontFace = fontFace,
  979.       fontSize = fontSize,
  980.       unid = unid,
  981.       puid = puid,
  982.       room = self
  983.     )
  984.     self._mqueue[i] = msg
  985.  
  986.   def _rcmd_u(self, args):
  987.     temp = Struct(**self._mqueue)
  988.     if hasattr(temp, args[0]):
  989.       msg = getattr(temp, args[0])
  990.       if msg.user != self.user:
  991.         msg.user._fontColor = msg.fontColor
  992.         msg.user._fontFace = msg.fontFace
  993.         msg.user._fontSize = msg.fontSize
  994.         msg.user._nameColor = msg.nameColor
  995.       del self._mqueue[args[0]]
  996.       msg.attach(self, args[1])
  997.       self._addHistory(msg)
  998.       self._callEvent("onMessage", msg.user, msg)
  999.  
  1000.   def _rcmd_i(self, args):
  1001.     mtime = float(args[0])
  1002.     puid = args[3]
  1003.     ip = args[6]
  1004.     name = args[1]
  1005.     rawmsg = ":".join(args[9:])
  1006.     msg, n, f = _clean_message(rawmsg)
  1007.     if name == "":
  1008.       nameColor = None
  1009.       name = "#" + args[2]
  1010.       if name == "#":
  1011.         name = "!anon" + _getAnonId(n, puid)
  1012.     else:
  1013.       if n: nameColor = _parseNameColor(n)
  1014.       else: nameColor = None
  1015.     i = args[5]
  1016.     unid = args[4]
  1017.     user = User(name)
  1018.     #Create an anonymous message and queue it because msgid is unknown.
  1019.     if f: fontColor, fontFace, fontSize = _parseFont(f)
  1020.     else: fontColor, fontFace, fontSize = None, None, None
  1021.     msg = Message(
  1022.       time = mtime,
  1023.       user = user,
  1024.       body = msg,
  1025.       raw = rawmsg,
  1026.       ip = ip,
  1027.       nameColor = nameColor,
  1028.       fontColor = fontColor,
  1029.       fontFace = fontFace,
  1030.       fontSize = fontSize,
  1031.       unid = unid,
  1032.       puid = puid,
  1033.       room = self
  1034.     )
  1035.     self._i_log.append(msg)
  1036.  
  1037.   def _rcmd_g_participants(self, args):
  1038.     args = ":".join(args)
  1039.     args = args.split(";")
  1040.     for data in args:
  1041.       data = data.split(":")
  1042.       name = data[3].lower()
  1043.       if name == "none": continue
  1044.       user = User(
  1045.         name = name,
  1046.         room = self
  1047.       )
  1048.       user.addSessionId(self, data[0])
  1049.       self._userlist.append(user)
  1050.  
  1051.   def _rcmd_participant(self, args):
  1052.     name = args[3].lower()
  1053.     if name == "none": return
  1054.     user = User(name)
  1055.     puid = args[2]
  1056.  
  1057.     if args[0] == "0": #leave
  1058.       user.removeSessionId(self, args[1])
  1059.       self._userlist.remove(user)
  1060.       if user not in self._userlist or not self.mgr._userlistEventUnique:
  1061.         self._callEvent("onLeave", user, puid)
  1062.     else: #join
  1063.       user.addSessionId(self, args[1])
  1064.       if user not in self._userlist: doEvent = True
  1065.       else: doEvent = False
  1066.       self._userlist.append(user)
  1067.       if doEvent or not self.mgr._userlistEventUnique:
  1068.         self._callEvent("onJoin", user, puid)
  1069.  
  1070.   def _rcmd_show_fw(self, args):
  1071.     self._callEvent("onFloodWarning")
  1072.  
  1073.   def _rcmd_show_tb(self, args):
  1074.     self._callEvent("onFloodBan")
  1075.  
  1076.   def _rcmd_tb(self, args):
  1077.     self._callEvent("onFloodBanRepeat")
  1078.  
  1079.   def _rcmd_delete(self, args):
  1080.     msg = self._msgs.get(args[0])
  1081.     if msg:
  1082.       if msg in self._history:
  1083.         self._history.remove(msg)
  1084.         self._callEvent("onMessageDelete", msg.user, msg)
  1085.         msg.detach()
  1086.  
  1087.   def _rcmd_deleteall(self, args):
  1088.     for msgid in args:
  1089.       self._rcmd_delete([msgid])
  1090.  
  1091.   def _rcmd_n(self, args):
  1092.     self._userCount = int(args[0], 16)
  1093.     self._callEvent("onUserCountChange")
  1094.  
  1095.   def _rcmd_blocklist(self, args):
  1096.     self._banlist = dict()
  1097.     sections = ":".join(args).split(";")
  1098.     for section in sections:
  1099.       params = section.split(":")
  1100.       if len(params) != 5: continue
  1101.       if params[2] == "": continue
  1102.       user = User(params[2])
  1103.       self._banlist[user] = {
  1104.         "unid":params[0],
  1105.         "ip":params[1],
  1106.         "target":user,
  1107.         "time":float(params[3]),
  1108.         "src":User(params[4])
  1109.       }
  1110.     self._callEvent("onBanlistUpdate")
  1111.  
  1112.   def _rcmd_unblocklist(self, args):
  1113.     self._unbanlist = dict()
  1114.     sections = ":".join(args).split(";")
  1115.     for section in sections:
  1116.       params = section.split(":")
  1117.       if len(params) != 5: continue
  1118.       if params[2] == "": continue
  1119.       user = User(params[2])
  1120.       self._unbanlist[user] = {
  1121.         "unid":params[0],
  1122.         "ip":params[1],
  1123.         "target":user,
  1124.         "time":float(params[3]),
  1125.         "src":User(params[4])
  1126.       }
  1127.     self._callEvent("onUnBanlistUpdate")
  1128.  
  1129.   def _rcmd_blocked(self, args):
  1130.     if args[2] == "": return
  1131.     target = User(args[2])
  1132.     user = User(args[3])
  1133.     self._banlist[target] = {"unid":args[0], "ip":args[1], "target":target, "time":float(args[4]), "src":user}
  1134.     self._callEvent("onBan", user, target)
  1135.  
  1136.   def _rcmd_unblocked(self, args):
  1137.     if args[2] == "": return
  1138.     target = User(args[2])
  1139.     user = User(args[3])
  1140.     del self._banlist[target]
  1141.     self._unbanlist[user] = {"unid":args[0], "ip":args[1], "target":target, "time":float(args[4]), "src":user}
  1142.     self._callEvent("onUnban", user, target)
  1143.  
  1144.   ####
  1145.   # Commands
  1146.   ####
  1147.   def login(self, NAME, PASS = None):
  1148.     """login as a user or set a name in room"""
  1149.     if PASS:
  1150.       self._sendCommand("blogin", NAME, PASS)
  1151.     else:
  1152.       self._sendCommand("blogin", NAME)
  1153.     self._currentname = NAME
  1154.  
  1155.   def logout(self):
  1156.     """logout of user in a room"""
  1157.     self._sendCommand("blogout")
  1158.     self._currentname = self._botname
  1159.  
  1160.   def ping(self):
  1161.     """Send a ping."""
  1162.     self._sendCommand("")
  1163.     self._callEvent("onPing")
  1164.  
  1165.   def rawMessage(self, msg):
  1166.     """
  1167.    Send a message without n and f tags.
  1168.  
  1169.    @type msg: str
  1170.    @param msg: message
  1171.    """
  1172.     if not self._silent:
  1173.       self._sendCommand("bmsg:tl2r", msg)
  1174.  
  1175.   def message(self, msg, html = False):
  1176.     """
  1177.    Send a message. (Use "\n" for new line)
  1178.  
  1179.    @type msg: str
  1180.    @param msg: message
  1181.    """
  1182.     if msg==None:
  1183.       return
  1184.     msg = msg.rstrip()
  1185.     if not html:
  1186.       msg = msg.replace("<", "&lt;").replace(">", "&gt;")
  1187.     if len(msg) > self.mgr._maxLength:
  1188.       if self.mgr._tooBigMessage == BigMessage_Cut:
  1189.         self.message(msg[:self.mgr._maxLength], html = html)
  1190.       elif self.mgr._tooBigMessage == BigMessage_Multiple:
  1191.         while len(msg) > 0:
  1192.           sect = msg[:self.mgr._maxLength]
  1193.           msg = msg[self.mgr._maxLength:]
  1194.           self.message(sect, html = html)
  1195.       return
  1196.     msg = "<n" + self.user.nameColor + "/>" + msg
  1197.     if self._currentname != None and not self._currentname.startswith("!anon"):
  1198.       font_properties = "<f x%0.2i%s=\"%s\">" %(self.user.fontSize, self.user.fontColor, self.user.fontFace)
  1199.       if "\n" in msg:
  1200.         msg.replace("\n", "</f></p><p>%s" %(font_properties))
  1201.       msg = font_properties + msg
  1202.     msg.replace("~","&#126;")
  1203.     self.rawMessage(msg)
  1204.  
  1205.   def setBgMode(self, mode):
  1206.     """turn on/off bg"""
  1207.     self._sendCommand("msgbg", str(mode))
  1208.  
  1209.   def setRecordingMode(self, mode):
  1210.     """turn on/off rcecording"""
  1211.     self._sendCommand("msgmedia", str(mode))
  1212.  
  1213.   def addMod(self, user):
  1214.     """
  1215.    Add a moderator.
  1216.  
  1217.    @type user: User
  1218.    @param user: User to mod.
  1219.    """
  1220.     if self.getLevel(User(self.currentname)) == 2:
  1221.       self._sendCommand("addmod", user.name)
  1222.  
  1223.   def removeMod(self, user):
  1224.     """
  1225.    Remove a moderator.
  1226.  
  1227.    @type user: User
  1228.    @param user: User to demod.
  1229.    """
  1230.     if self.getLevel(User(self.currentname)) == 2:
  1231.       self._sendCommand("removemod", user.name)
  1232.  
  1233.   def flag(self, message):
  1234.     """
  1235.    Flag a message.
  1236.  
  1237.    @type message: Message
  1238.    @param message: message to flag
  1239.    """
  1240.     self._sendCommand("g_flag", message.msgid)
  1241.  
  1242.   def flagUser(self, user):
  1243.     """
  1244.    Flag a user.
  1245.  
  1246.    @type user: User
  1247.    @param user: user to flag
  1248.  
  1249.    @rtype: bool
  1250.    @return: whether a message to flag was found
  1251.    """
  1252.     msg = self.getLastMessage(user)
  1253.     if msg:
  1254.       self.flag(msg)
  1255.       return True
  1256.     return False
  1257.  
  1258.   def deleteMessage(self, message):
  1259.     """
  1260.    Delete a message. (Moderator only)
  1261.  
  1262.    @type message: Message
  1263.    @param message: message to delete
  1264.    """
  1265.     if self.getLevel(self.user) > 0:
  1266.       self._sendCommand("delmsg", message.msgid)
  1267.  
  1268.   def deleteUser(self, user):
  1269.     """
  1270.    Delete a message. (Moderator only)
  1271.  
  1272.    @type message: User
  1273.    @param message: delete user's last message
  1274.    """
  1275.     if self.getLevel(self.user) > 0:
  1276.       msg = self.getLastMessage(user)
  1277.       if msg:
  1278.         self._sendCommand("delmsg", msg.msgid)
  1279.       return True
  1280.     return False
  1281.  
  1282.   def delete(self, message):
  1283.     """
  1284.    compatibility wrapper for deleteMessage
  1285.    """
  1286.     print("[obsolete] the delete function is obsolete, please use deleteMessage")
  1287.     return self.deleteMessage(message)
  1288.  
  1289.   def rawClearUser(self, unid, ip, user):
  1290.     self._sendCommand("delallmsg", unid, ip, user)
  1291.   def clearUser(self, user):
  1292.     """
  1293.    Clear all of a user's messages. (Moderator only)
  1294.  
  1295.    @type user: User
  1296.    @param user: user to delete messages of
  1297.  
  1298.    @rtype: bool
  1299.    @return: whether a message to delete was found
  1300.    """
  1301.     if self.getLevel(self.user) > 0:
  1302.       msg = self.getLastMessage(user)
  1303.       if msg:
  1304.         if msg.user.name[0] in ["!","#"]:self.rawClearUser(msg.unid, msg.ip,"")
  1305.         else:self.rawClearUser(msg.unid,msg.ip,msg.user.name)
  1306.         return True
  1307.     return False
  1308.  
  1309.   def clearall(self):
  1310.     """Clear all messages. (Owner only)"""
  1311.     if self.getLevel(self.user) == 2:
  1312.       self._sendCommand("clearall")
  1313.  
  1314.   def rawBan(self, name, ip, unid):
  1315.     """
  1316.    Execute the block command using specified arguments.
  1317.    (For advanced usage)
  1318.  
  1319.    @type name: str
  1320.    @param name: name
  1321.    @type ip: str
  1322.    @param ip: ip address
  1323.    @type unid: str
  1324.    @param unid: unid
  1325.    """
  1326.     self._sendCommand("block", unid, ip, name)
  1327.  
  1328.   def ban(self, msg):
  1329.     """
  1330.    Ban a message's sender. (Moderator only)
  1331.  
  1332.    @type message: Message
  1333.    @param message: message to ban sender of
  1334.    """
  1335.     if self.getLevel(self.user) > 0:
  1336.       self.rawBan(msg.user.name, msg.ip, msg.unid)
  1337.  
  1338.   def banUser(self, user):
  1339.     """
  1340.    Ban a user. (Moderator only)
  1341.  
  1342.    @type user: User
  1343.    @param user: user to ban
  1344.  
  1345.    @rtype: bool
  1346.    @return: whether a message to ban the user was found
  1347.    """
  1348.     msg = self.getLastMessage(user)
  1349.     if msg:
  1350.       self.ban(msg)
  1351.       return True
  1352.     return False
  1353.  
  1354.   def requestBanlist(self):
  1355.     """Request an updated banlist."""
  1356.     self._sendCommand("blocklist", "block", "", "next", "500")
  1357.  
  1358.   def requestUnBanlist(self):
  1359.     """Request an updated banlist."""
  1360.     self._sendCommand("blocklist", "unblock", "", "next", "500")
  1361.  
  1362.   def rawUnban(self, name, ip, unid):
  1363.     """
  1364.    Execute the unblock command using specified arguments.
  1365.    (For advanced usage)
  1366.  
  1367.    @type name: str
  1368.    @param name: name
  1369.    @type ip: str
  1370.    @param ip: ip address
  1371.    @type unid: str
  1372.    @param unid: unid
  1373.    """
  1374.     self._sendCommand("removeblock", unid, ip, name)
  1375.  
  1376.   def unban(self, user):
  1377.     """
  1378.    Unban a user. (Moderator only)
  1379.  
  1380.    @type user: User
  1381.    @param user: user to unban
  1382.  
  1383.    @rtype: bool
  1384.    @return: whether it succeeded
  1385.    """
  1386.     rec = self._getBanRecord(user)
  1387.     if rec:
  1388.       self.rawUnban(rec["target"].name, rec["ip"], rec["unid"])
  1389.       return True
  1390.     else:
  1391.       return False
  1392.  
  1393.   ####
  1394.   # Util
  1395.   ####
  1396.   def _getBanRecord(self, user):
  1397.     if user in self._banlist:
  1398.       return self._banlist[user]
  1399.     return None
  1400.  
  1401.   def _callEvent(self, evt, *args, **kw):
  1402.     getattr(self.mgr, evt)(self, *args, **kw)
  1403.     self.mgr.onEventCalled(self, evt, *args, **kw)
  1404.  
  1405.   def _write(self, data):
  1406.     if self._wlock:
  1407.       self._wlockbuf += data
  1408.     else:
  1409.       self.mgr._write(self, data)
  1410.  
  1411.   def _setWriteLock(self, lock):
  1412.     self._wlock = lock
  1413.     if self._wlock == False:
  1414.       self._write(self._wlockbuf)
  1415.       self._wlockbuf = b""
  1416.  
  1417.   def _sendCommand(self, *args):
  1418.     """
  1419.    Send a command.
  1420.  
  1421.    @type args: [str, str, ...]
  1422.    @param args: command and list of arguments
  1423.    """
  1424.     if self._firstCommand:
  1425.       terminator = b"\x00"
  1426.       self._firstCommand = False
  1427.     else:
  1428.       terminator = b"\r\n\x00"
  1429.     self._write(":".join(args).encode() + terminator)
  1430.  
  1431.   def getLevel(self, user):
  1432.     """get the level of user in a room"""
  1433.     if user == self._owner: return 2
  1434.     if user.name in self.modnames: return 1
  1435.     return 0
  1436.  
  1437.   def getLastMessage(self, user = None):
  1438.     """get last message said by user in a room"""
  1439.     if user:
  1440.       try:
  1441.         i = 1
  1442.         while True:
  1443.           msg = self._history[-i]
  1444.           if msg.user == user:
  1445.             return msg
  1446.           i += 1
  1447.       except IndexError:
  1448.         return None
  1449.     else:
  1450.       try:
  1451.         return self._history[-1]
  1452.       except IndexError:
  1453.         return None
  1454.     return None
  1455.  
  1456.   def findUser(self, name):
  1457.     """check if user is in the room
  1458.    
  1459.    return User(name) if name in room else None"""
  1460.     name = name.lower()
  1461.     ul = self._getUserlist()
  1462.     udi = dict(zip([u.name for u in ul], ul))
  1463.     cname = None
  1464.     for n in udi.keys():
  1465.       if name in n:
  1466.         if cname: return None #ambiguous!!
  1467.         cname = n
  1468.     if cname: return udi[cname]
  1469.     else: return None
  1470.  
  1471.   ####
  1472.   # History
  1473.   ####
  1474.   def _addHistory(self, msg):
  1475.     """
  1476.    Add a message to history.
  1477.  
  1478.    @type msg: Message
  1479.    @param msg: message
  1480.    """
  1481.     self._history.append(msg)
  1482.     if len(self._history) > self.mgr._maxHistoryLength:
  1483.       rest, self._history = self._history[:-self.mgr._maxHistoryLength], self._history[-self.mgr._maxHistoryLength:]
  1484.       for msg in rest: msg.detach()
  1485.  
  1486. #####################
  1487. # RoomManager class #
  1488. #####################
  1489.  
  1490. class RoomManager:
  1491.   """Class that manages multiple connections."""
  1492.   ####
  1493.   # Config
  1494.   ####
  1495.   _Room = Room
  1496.   _PM = PM
  1497.   _ANON_PM = ANON_PM
  1498.   _anonPMHost = "b1.chatango.com"
  1499.   _PMHost = "c1.chatango.com"
  1500.   _PMPort = 5222
  1501.   _TimerResolution = 0.2 #at least x times per second
  1502.   _pingDelay = 20
  1503.   _userlistMode = Userlist_Recent
  1504.   _userlistUnique = True
  1505.   _userlistMemory = 50
  1506.   _userlistEventUnique = False
  1507.   _tooBigMessage = BigMessage_Multiple
  1508.   _maxLength = 1800
  1509.   _maxHistoryLength = 150
  1510.  
  1511.   ####
  1512.   # Init
  1513.   ####
  1514.   def __init__(self, name = None, password = None, pm = True):
  1515.     self._name = name
  1516.     self._password = password
  1517.     self._running = False
  1518.     self._tasks = set()
  1519.     self._rooms = dict()
  1520.     self._rooms_queue = queue.Queue()
  1521.     self._rooms_lock = threading.Lock()
  1522.     if pm:
  1523.       if self._password:
  1524.         self._pm = self._PM(mgr = self)
  1525.       else:
  1526.         self._pm = self._ANON_PM(mgr = self)
  1527.     else:
  1528.       self._pm = None
  1529.  
  1530.   def _joinThread(self):
  1531.     while True:
  1532.       room = self._rooms_queue.get()
  1533.       with self._rooms_lock:
  1534.         con = self._Room(room, mgr = self)
  1535.         self._rooms[room] = con
  1536.  
  1537.   ####
  1538.   # Join/leave
  1539.   ####
  1540.   def joinRoom(self, room):
  1541.     """
  1542.    Join a room or return None if already joined.
  1543.  
  1544.    @type room: str
  1545.    @param room: room to join
  1546.  
  1547.    @rtype: Room or None
  1548.    @return: True or nothing
  1549.    """
  1550.     room = room.lower()
  1551.     if room not in self._rooms:
  1552.       self._rooms_queue.put(room)
  1553.       return True
  1554.     else:
  1555.       return None
  1556.  
  1557.   def leaveRoom(self, room):
  1558.     """
  1559.    Leave a room.
  1560.  
  1561.    @type room: str
  1562.    @param room: room to leave
  1563.    """
  1564.     room = room.lower()
  1565.     if room in self._rooms:
  1566.       with self._rooms_lock:
  1567.         con = self._rooms[room]
  1568.         con.disconnect()
  1569.  
  1570.   def getRoom(self, room):
  1571.     """
  1572.    Get room with a name, or None if not connected to this room.
  1573.  
  1574.    @type room: str
  1575.    @param room: room
  1576.  
  1577.    @rtype: Room
  1578.    @return: the room
  1579.    """
  1580.     room = room.lower()
  1581.     if room in self._rooms:
  1582.       return self._rooms[room]
  1583.     else:
  1584.       return None
  1585.  
  1586.   ####
  1587.   # Properties
  1588.   ####
  1589.   def _getUser(self): return User(self._name)
  1590.   def _getName(self): return self._name
  1591.   def _getPassword(self): return self._password
  1592.   def _getRooms(self): return set(self._rooms.values())
  1593.   def _getRoomNames(self): return set(self._rooms.keys())
  1594.   def _getPM(self): return self._pm
  1595.  
  1596.   user = property(_getUser)
  1597.   name = property(_getName)
  1598.   password = property(_getPassword)
  1599.   rooms = property(_getRooms)
  1600.   roomnames = property(_getRoomNames)
  1601.   pm = property(_getPM)
  1602.  
  1603.   ####
  1604.   # Virtual methods
  1605.   ####
  1606.   def onInit(self):
  1607.     """Called on init."""
  1608.     pass
  1609.  
  1610.   def safePrint(self, text):
  1611.     """Use this to safely print text with unicode"""
  1612.     while True:
  1613.       try:
  1614.         print(text)
  1615.         break
  1616.       except UnicodeEncodeError as ex:
  1617.         text = (text[0:ex.start]+'(unicode)'+text[ex.end:])
  1618.  
  1619.   def onConnect(self, room):
  1620.     """
  1621.    Called when connected to the room.
  1622.    
  1623.    @type room: Room
  1624.    @param room: room where the event occured
  1625.    """
  1626.     pass
  1627.  
  1628.   def onReconnect(self, room):
  1629.     """
  1630.    Called when reconnected to the room.
  1631.    
  1632.    @type room: Room
  1633.    @param room: room where the event occured
  1634.    """
  1635.     pass
  1636.  
  1637.   def onConnectFail(self, room):
  1638.     """
  1639.    Called when the connection failed.
  1640.    
  1641.    @type room: Room
  1642.    @param room: room where the event occured
  1643.    """
  1644.     pass
  1645.  
  1646.   def onDisconnect(self, room):
  1647.     """
  1648.    Called when the client gets disconnected.
  1649.    
  1650.    @type room: Room
  1651.    @param room: room where the event occured
  1652.    """
  1653.     pass
  1654.  
  1655.   def onLoginFail(self, room):
  1656.     """
  1657.    Called on login failure, disconnects after.
  1658.    
  1659.    @type room: Room
  1660.    @param room: room where the event occured
  1661.    """
  1662.     pass
  1663.  
  1664.   def onFloodBan(self, room):
  1665.     """
  1666.    Called when either flood banned or flagged.
  1667.    
  1668.    @type room: Room
  1669.    @param room: room where the event occured
  1670.    """
  1671.     pass
  1672.  
  1673.   def onFloodBanRepeat(self, room):
  1674.     """
  1675.    Called when trying to send something when floodbanned.
  1676.  
  1677.    @type room: Room
  1678.    @param room: room where the event occured
  1679.    """
  1680.     pass
  1681.  
  1682.   def onFloodWarning(self, room):
  1683.     """
  1684.    Called when an overflow warning gets received.
  1685.  
  1686.    @type room: Room
  1687.    @param room: room where the event occured
  1688.    """
  1689.     pass
  1690.  
  1691.   def onMessageDelete(self, room, user, message):
  1692.     """
  1693.    Called when a message gets deleted.
  1694.  
  1695.    @type room: Room
  1696.    @param room: room where the event occured
  1697.    @type user: User
  1698.    @param user: owner of deleted message
  1699.    @type message: Message
  1700.    @param message: message that got deleted
  1701.    """
  1702.     pass
  1703.  
  1704.   def onModChange(self, room):
  1705.     """
  1706.    Called when the moderator list changes.
  1707.  
  1708.    @type room: Room
  1709.    @param room: room where the event occured
  1710.    """
  1711.     pass
  1712.  
  1713.   def onModAdd(self, room, user):
  1714.     """
  1715.    Called when a moderator gets added.
  1716.  
  1717.    @type room: Room
  1718.    @param room: room where the event occured
  1719.    """
  1720.     pass
  1721.  
  1722.   def onModRemove(self, room, user):
  1723.     """
  1724.    Called when a moderator gets removed.
  1725.  
  1726.    @type room: Room
  1727.    @param room: room where the event occured
  1728.    """
  1729.     pass
  1730.  
  1731.   def onMessage(self, room, user, message):
  1732.     """
  1733.    Called when a message gets received.
  1734.  
  1735.    @type room: Room
  1736.    @param room: room where the event occured
  1737.    @type user: User
  1738.    @param user: owner of message
  1739.    @type message: Message
  1740.    @param message: received message
  1741.    """
  1742.     pass
  1743.  
  1744.   def onHistoryMessage(self, room, user, message):
  1745.     """
  1746.    Called when a message gets received from history.
  1747.  
  1748.    @type room: Room
  1749.    @param room: room where the event occured
  1750.    @type user: User
  1751.    @param user: owner of message
  1752.    @type message: Message
  1753.    @param message: the message that got added
  1754.    """
  1755.     pass
  1756.  
  1757.   def onJoin(self, room, user, puid):
  1758.     """
  1759.    Called when a user joins. Anonymous users get ignored here.
  1760.  
  1761.    @type room: Room
  1762.    @param room: room where the event occured
  1763.    @type user: User
  1764.    @param user: the user that has joined
  1765.    @type puid: str
  1766.    @param puid: the personal unique id for the user
  1767.    """
  1768.     pass
  1769.  
  1770.   def onLeave(self, room, user, puid):
  1771.     """
  1772.    Called when a user leaves. Anonymous users get ignored here.
  1773.  
  1774.    @type room: Room
  1775.    @param room: room where the event occured
  1776.    @type user: User
  1777.    @param user: the user that has left
  1778.    @type puid: str
  1779.    @param puid: the personal unique id for the user
  1780.    """
  1781.     pass
  1782.  
  1783.   def onRaw(self, room, raw):
  1784.     """
  1785.    Called before any command parsing occurs.
  1786.  
  1787.    @type room: Room
  1788.    @param room: room where the event occured
  1789.    @type raw: str
  1790.    @param raw: raw command data
  1791.    """
  1792.     pass
  1793.  
  1794.   def onPing(self, room):
  1795.     """
  1796.    Called when a ping gets sent.
  1797.  
  1798.    @type room: Room
  1799.    @param room: room where the event occured
  1800.    """
  1801.     pass
  1802.  
  1803.   def onUserCountChange(self, room):
  1804.     """
  1805.    Called when the user count changes.
  1806.  
  1807.    @type room: Room
  1808.    @param room: room where the event occured
  1809.    """
  1810.     pass
  1811.  
  1812.   def onBan(self, room, user, target):
  1813.     """
  1814.    Called when a user gets banned.
  1815.  
  1816.    @type room: Room
  1817.    @param room: room where the event occured
  1818.    @type user: User
  1819.    @param user: user that banned someone
  1820.    @type target: User
  1821.    @param target: user that got banned
  1822.    """
  1823.     pass
  1824.  
  1825.   def onUnban(self, room, user, target):
  1826.     """
  1827.    Called when a user gets unbanned.
  1828.  
  1829.    @type room: Room
  1830.    @param room: room where the event occured
  1831.    @type user: User
  1832.    @param user: user that unbanned someone
  1833.    @type target: User
  1834.    @param target: user that got unbanned
  1835.    """
  1836.     pass
  1837.  
  1838.   def onBanlistUpdate(self, room):
  1839.     """
  1840.    Called when a banlist gets updated.
  1841.  
  1842.    @type room: Room
  1843.    @param room: room where the event occured
  1844.    """
  1845.     pass
  1846.  
  1847.   def onUnBanlistUpdate(self, room):
  1848.     """
  1849.    Called when a unbanlist gets updated.
  1850.  
  1851.    @type room: Room
  1852.    @param room: room where the event occured
  1853.    """
  1854.     pass
  1855.  
  1856.   def onPMConnect(self, pm):
  1857.     """
  1858.    Called when connected to the pm
  1859.    
  1860.    @type pm: PM
  1861.    @param pm: the pm
  1862.    """
  1863.     pass
  1864.  
  1865.   def onAnonPMDisconnect(self, pm, user):
  1866.     """
  1867.    Called when disconnected from the pm
  1868.    
  1869.    @type pm: PM
  1870.    @param pm: the pm
  1871.    """
  1872.     pass
  1873.  
  1874.   def onPMDisconnect(self, pm):
  1875.     """
  1876.    Called when disconnected from the pm
  1877.    
  1878.    @type pm: PM
  1879.    @param pm: the pm
  1880.    """
  1881.     pass
  1882.  
  1883.   def onPMPing(self, pm):
  1884.     """
  1885.    Called when sending a ping to the pm
  1886.    
  1887.    @type pm: PM
  1888.    @param pm: the pm
  1889.    """
  1890.     pass
  1891.  
  1892.   def onPMMessage(self, pm, user, body):
  1893.     """
  1894.    Called when a message is received
  1895.    
  1896.    @type pm: PM
  1897.    @param pm: the pm
  1898.    @type user: User
  1899.    @param user: owner of message
  1900.    @type message: Message
  1901.    @param message: received message
  1902.    """
  1903.     pass
  1904.  
  1905.   def onPMOfflineMessage(self, pm, user, body):
  1906.     """
  1907.    Called when connected if a message is received while offline
  1908.    
  1909.    @type pm: PM
  1910.    @param pm: the pm
  1911.    @type user: User
  1912.    @param user: owner of message
  1913.    @type message: Message
  1914.    @param message: received message
  1915.    """
  1916.     pass
  1917.  
  1918.   def onPMContactlistReceive(self, pm):
  1919.     """
  1920.    Called when the contact list is received
  1921.    
  1922.    @type pm: PM
  1923.    @param pm: the pm
  1924.    """
  1925.     pass
  1926.  
  1927.   def onPMBlocklistReceive(self, pm):
  1928.     """
  1929.    Called when the block list is received
  1930.    
  1931.    @type pm: PM
  1932.    @param pm: the pm
  1933.    """
  1934.     pass
  1935.  
  1936.   def onPMContactAdd(self, pm, user):
  1937.     """
  1938.    Called when the contact added message is received
  1939.    
  1940.    @type pm: PM
  1941.    @param pm: the pm
  1942.    @type user: User
  1943.    @param user: the user that gotten added
  1944.    """
  1945.     pass
  1946.  
  1947.   def onPMContactRemove(self, pm, user):
  1948.     """
  1949.    Called when the contact remove message is received
  1950.    
  1951.    @type pm: PM
  1952.    @param pm: the pm
  1953.    @type user: User
  1954.    @param user: the user that gotten remove
  1955.    """
  1956.     pass
  1957.  
  1958.   def onPMBlock(self, pm, user):
  1959.     """
  1960.    Called when successfully block a user
  1961.    
  1962.    @type pm: PM
  1963.    @param pm: the pm
  1964.    @type user: User
  1965.    @param user: the user that gotten block
  1966.    """
  1967.     pass
  1968.  
  1969.   def onPMUnblock(self, pm, user):
  1970.     """
  1971.    Called when successfully unblock a user
  1972.    
  1973.    @type pm: PM
  1974.    @param pm: the pm
  1975.    @type user: User
  1976.    @param user: the user that gotten unblock
  1977.    """
  1978.     pass
  1979.  
  1980.   def onPMContactOnline(self, pm, user):
  1981.     """
  1982.    Called when a user from the contact come online
  1983.    
  1984.    @type pm: PM
  1985.    @param pm: the pm
  1986.    @type user: User
  1987.    @param user: the user that came online
  1988.    """
  1989.     pass
  1990.  
  1991.   def onPMContactOffline(self, pm, user):
  1992.     """
  1993.    Called when a user from the contact go offline
  1994.    
  1995.    @type pm: PM
  1996.    @param pm: the pm
  1997.    @type user: User
  1998.    @param user: the user that went offline
  1999.    """
  2000.     pass
  2001.  
  2002.   def onEventCalled(self, room, evt, *args, **kw):
  2003.     """
  2004.    Called on every room-based event.
  2005.  
  2006.    @type room: Room
  2007.    @param room: room where the event occured
  2008.    @type evt: str
  2009.    @param evt: the event
  2010.    """
  2011.     pass
  2012.  
  2013.   ####
  2014.   # Deferring
  2015.   ####
  2016.   def deferToThread(self, callback, func, *args, **kw):
  2017.     """
  2018.    Defer a function to a thread and callback the return value.
  2019.  
  2020.    @type callback: function
  2021.    @param callback: function to call on completion
  2022.    @type cbargs: tuple or list
  2023.    @param cbargs: arguments to get supplied to the callback
  2024.    @type func: function
  2025.    @param func: function to call
  2026.    """
  2027.     def f(func, callback, *args, **kw):
  2028.       ret = func(*args, **kw)
  2029.       self.setTimeout(0, callback, ret)
  2030.     threading._start_new_thread(f, (func, callback) + args, kw)
  2031.  
  2032.   ####
  2033.   # Scheduling
  2034.   ####
  2035.   class _Task:
  2036.     def cancel(self):
  2037.       """Sugar for removeTask."""
  2038.       self.mgr.removeTask(self)
  2039.  
  2040.   def _tick(self):
  2041.     now = time.time()
  2042.     for task in set(self._tasks):
  2043.       if task.target <= now:
  2044.         task.func(*task.args, **task.kw)
  2045.         if task.isInterval:
  2046.           task.target = now + task.timeout
  2047.         else:
  2048.           self._tasks.remove(task)
  2049.  
  2050.   def setTimeout(self, timeout, func, *args, **kw):
  2051.     """
  2052.    Call a function after at least timeout seconds with specified arguments.
  2053.  
  2054.    @type timeout: int
  2055.    @param timeout: timeout
  2056.    @type func: function
  2057.    @param func: function to call
  2058.  
  2059.    @rtype: _Task
  2060.    @return: object representing the task
  2061.    """
  2062.     task = self._Task()
  2063.     task.mgr = self
  2064.     task.target = time.time() + timeout
  2065.     task.timeout = timeout
  2066.     task.func = func
  2067.     task.isInterval = False
  2068.     task.args = args
  2069.     task.kw = kw
  2070.     self._tasks.add(task)
  2071.     return task
  2072.  
  2073.   def setInterval(self, timeout, func, *args, **kw):
  2074.     """
  2075.    Call a function at least every timeout seconds with specified arguments.
  2076.  
  2077.    @type timeout: int
  2078.    @param timeout: timeout
  2079.    @type func: function
  2080.    @param func: function to call
  2081.  
  2082.    @rtype: _Task
  2083.    @return: object representing the task
  2084.    """
  2085.     task = self._Task()
  2086.     task.mgr = self
  2087.     task.target = time.time() + timeout
  2088.     task.timeout = timeout
  2089.     task.func = func
  2090.     task.isInterval = True
  2091.     task.args = args
  2092.     task.kw = kw
  2093.     self._tasks.add(task)
  2094.     return task
  2095.  
  2096.   def removeTask(self, task):
  2097.     """
  2098.    Cancel a task.
  2099.  
  2100.    @type task: _Task
  2101.    @param task: task to cancel
  2102.    """
  2103.     self._tasks.remove(task)
  2104.  
  2105.   ####
  2106.   # Util
  2107.   ####
  2108.   def _write(self, room, data):
  2109.     room._wbuf += data
  2110.  
  2111.   def getConnections(self):
  2112.     li = list(self._rooms.values())
  2113.     if self._pm:
  2114.       li.extend(self._pm.getConnections())
  2115.     return [c for c in li if c._sock != None]
  2116.  
  2117.   ####
  2118.   # Main
  2119.   ####
  2120.   def main(self):
  2121.     self.onInit()
  2122.     self._running = True
  2123.     for l in range(0,Number_of_Threads):
  2124.       t = threading.Thread(target=self._joinThread)
  2125.       t.daemon = True
  2126.       t.start()
  2127.     while self._running:
  2128.       conns = self.getConnections()
  2129.       socks = [x._sock for x in conns]
  2130.       wsocks = [x._sock for x in conns if x._wbuf != b""]
  2131.       rd, wr, sp = select.select(socks, wsocks, [], self._TimerResolution)
  2132.       for sock in rd:
  2133.         con = [c for c in conns if c._sock == sock][0]
  2134.         try:
  2135.           data = sock.recv(1024)
  2136.           if(len(data) > 0):
  2137.             con._feed(data)
  2138.           else:
  2139.             con.disconnect()
  2140.         except socket.error:
  2141.           pass
  2142.       for sock in wr:
  2143.         con = [c for c in conns if c._sock == sock][0]
  2144.         try:
  2145.           size = sock.send(con._wbuf)
  2146.           con._wbuf = con._wbuf[size:]
  2147.         except socket.error:
  2148.           pass
  2149.       self._tick()
  2150.  
  2151.   @classmethod
  2152.   def easy_start(cl, rooms = None, name = None, password = None, pm = True):
  2153.     """
  2154.    Prompts the user for missing info, then starts.
  2155.  
  2156.    @type rooms: list
  2157.    @param room: rooms to join
  2158.    @type name: str
  2159.    @param name: name to join as ("" = None, None = unspecified)
  2160.    @type password: str
  2161.    @param password: password to join with ("" = None, None = unspecified)
  2162.    """
  2163.     if not rooms: rooms = str(input("Room names separated by semicolons: ")).split(";")
  2164.     if len(rooms) == 1 and rooms[0] == "": rooms = []
  2165.     if not name: name = str(input("User name: "))
  2166.     if name == "": name = None
  2167.     if not password: password = str(input("User password: "))
  2168.     if password == "": password = None
  2169.     self = cl(name, password, pm = pm)
  2170.     for room in rooms:
  2171.       self.joinRoom(room)
  2172.     self.main()
  2173.  
  2174.   def stop(self):
  2175.     for conn in list(self._rooms.values()):
  2176.       conn.disconnect()
  2177.     self._running = False
  2178.  
  2179.   ####
  2180.   # Commands
  2181.   ####
  2182.   def enableBg(self):
  2183.     """Enable background if available."""
  2184.     self.user._mbg = True
  2185.     for room in self.rooms:
  2186.       room.setBgMode(1)
  2187.  
  2188.   def disableBg(self):
  2189.     """Disable background."""
  2190.     self.user._mbg = False
  2191.     for room in self.rooms:
  2192.       room.setBgMode(0)
  2193.  
  2194.   def enableRecording(self):
  2195.     """Enable recording if available."""
  2196.     self.user._mrec = True
  2197.     for room in self.rooms:
  2198.       room.setRecordingMode(1)
  2199.  
  2200.   def disableRecording(self):
  2201.     """Disable recording."""
  2202.     self.user._mrec = False
  2203.     for room in self.rooms:
  2204.       room.setRecordingMode(0)
  2205.  
  2206.   def setNameColor(self, color3x):
  2207.     """
  2208.    Set name color.
  2209.  
  2210.    @type color3x: str
  2211.    @param color3x: a 3-char RGB hex code for the color
  2212.    """
  2213.     self.user._nameColor = color3x
  2214.  
  2215.   def setFontColor(self, color3x):
  2216.     """
  2217.    Set font color.
  2218.  
  2219.    @type color3x: str
  2220.    @param color3x: a 3-char RGB hex code for the color
  2221.    """
  2222.     self.user._fontColor = color3x
  2223.  
  2224.   def setFontFace(self, face):
  2225.     """
  2226.    Set font face/family.
  2227.  
  2228.    @type face: str
  2229.    @param face: the font face
  2230.    """
  2231.     self.user._fontFace = face
  2232.  
  2233.   def setFontSize(self, size):
  2234.     """
  2235.    Set font size.
  2236.  
  2237.    @type size: int
  2238.    @param size: the font size (limited: 9 to 22)
  2239.    """
  2240.     if size < 9: size = 9
  2241.     if size > 22: size = 22
  2242.     self.user._fontSize = size
  2243.  
  2244. ##############
  2245. # User class #
  2246. ##############
  2247.  
  2248. _users = dict()
  2249. def User(name, *args, **kw):
  2250.   if name == None: name = ""
  2251.   name = name.lower()
  2252.   user = _users.get(name)
  2253.   if not user:
  2254.     user = _User(name = name, *args, **kw)
  2255.     _users[name] = user
  2256.   return user
  2257.  
  2258. class _User:
  2259.   """Class that represents a user."""
  2260.   ####
  2261.   # Init
  2262.   ####
  2263.   def __init__(self, name, **kw):
  2264.     self._name = name.lower()
  2265.     self._sids = dict()
  2266.     self._msgs = list()
  2267.     self._nameColor = "000"
  2268.     self._fontSize = 12
  2269.     self._fontFace = "0"
  2270.     self._fontColor = "000"
  2271.     self._mbg = False
  2272.     self._mrec = False
  2273.     for attr, val in kw.items():
  2274.       if val == None: continue
  2275.       setattr(self, "_" + attr, val)
  2276.  
  2277.   ####
  2278.   # Properties
  2279.   ####
  2280.   def _getName(self): return self._name
  2281.   def _getSessionIds(self, room = None):
  2282.     if room:
  2283.       return self._sids.get(room, set())
  2284.     else:
  2285.       return set.union(*self._sids.values())
  2286.   def _getRooms(self): return self._sids.keys()
  2287.   def _getRoomNames(self): return [room.name for room in self._getRooms()]
  2288.   def _getFontColor(self): return self._fontColor
  2289.   def _getFontFace(self): return self._fontFace
  2290.   def _getFontSize(self): return self._fontSize
  2291.   def _getNameColor(self): return self._nameColor
  2292.  
  2293.   name = property(_getName)
  2294.   sessionids = property(_getSessionIds)
  2295.   rooms = property(_getRooms)
  2296.   roomnames = property(_getRoomNames)
  2297.   fontColor = property(_getFontColor)
  2298.   fontFace = property(_getFontFace)
  2299.   fontSize = property(_getFontSize)
  2300.   nameColor = property(_getNameColor)
  2301.  
  2302.   ####
  2303.   # Util
  2304.   ####
  2305.   def addSessionId(self, room, sid):
  2306.     if room not in self._sids:
  2307.       self._sids[room] = set()
  2308.     self._sids[room].add(sid)
  2309.  
  2310.   def removeSessionId(self, room, sid):
  2311.     try:
  2312.       self._sids[room].remove(sid)
  2313.       if len(self._sids[room]) == 0:
  2314.         del self._sids[room]
  2315.     except KeyError:
  2316.       pass
  2317.  
  2318.   def clearSessionIds(self, room):
  2319.     try:
  2320.       del self._sids[room]
  2321.     except KeyError:
  2322.       pass
  2323.  
  2324.   def hasSessionId(self, room, sid):
  2325.     try:
  2326.       if sid in self._sids[room]:
  2327.         return True
  2328.       else:
  2329.         return False
  2330.     except KeyError:
  2331.       return False
  2332.  
  2333.   ####
  2334.   # Repr
  2335.   ####
  2336.   def __repr__(self):
  2337.     return "<User: %s>" %(self.name)
  2338.  
  2339. #################
  2340. # Message class #
  2341. #################
  2342. class Message:
  2343.   """Class that represents a message."""
  2344.   ####
  2345.   # Attach/detach
  2346.   ####
  2347.   def attach(self, room, msgid):
  2348.     """
  2349.    Attach the Message to a message id.
  2350.  
  2351.    @type msgid: str
  2352.    @param msgid: message id
  2353.    """
  2354.     if self._msgid == None:
  2355.       self._room = room
  2356.       self._msgid = msgid
  2357.       self._room._msgs[msgid] = self
  2358.  
  2359.   def detach(self):
  2360.     """Detach the Message."""
  2361.     if self._msgid != None and self._msgid in self._room._msgs:
  2362.       del self._room._msgs[self._msgid]
  2363.       self._msgid = None
  2364.  
  2365.   def delete(self):
  2366.     self._room.deleteMessage(self)
  2367.  
  2368.   ####
  2369.   # Init
  2370.   ####
  2371.   def __init__(self, **kw):
  2372.     """init, don't overwrite"""
  2373.     self._msgid = None
  2374.     self._time = None
  2375.     self._user = None
  2376.     self._body = None
  2377.     self._room = None
  2378.     self._raw = ""
  2379.     self._ip = None
  2380.     self._unid = ""
  2381.     self._puid = ""
  2382.     self._uid = ""
  2383.     self._nameColor = "000"
  2384.     self._fontSize = 12
  2385.     self._fontFace = "0"
  2386.     self._fontColor = "000"
  2387.     for attr, val in kw.items():
  2388.       if val == None: continue
  2389.       setattr(self, "_" + attr, val)
  2390.  
  2391.   ####
  2392.   # Properties
  2393.   ####
  2394.   def _getId(self): return self._msgid
  2395.   def _getTime(self): return self._time
  2396.   def _getUser(self): return self._user
  2397.   def _getBody(self): return self._body
  2398.   def _getIP(self): return self._ip
  2399.   def _getFontColor(self): return self._fontColor
  2400.   def _getFontFace(self): return self._fontFace
  2401.   def _getFontSize(self): return self._fontSize
  2402.   def _getNameColor(self): return self._nameColor
  2403.   def _getRoom(self): return self._room
  2404.   def _getRaw(self): return self._raw
  2405.   def _getUnid(self): return self._unid
  2406.   def _getPuid(self): return self._puid
  2407.  
  2408.   msgid = property(_getId)
  2409.   time = property(_getTime)
  2410.   user = property(_getUser)
  2411.   body = property(_getBody)
  2412.   room = property(_getRoom)
  2413.   ip = property(_getIP)
  2414.   fontColor = property(_getFontColor)
  2415.   fontFace = property(_getFontFace)
  2416.   fontSize = property(_getFontSize)
  2417.   raw = property(_getRaw)
  2418.   nameColor = property(_getNameColor)
  2419.   unid = property(_getUnid)
  2420.   puid = property(_getPuid)
  2421.   uid = property(_getPuid) # other library use uid so we create an alias
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement