Advertisement
0rx

ch.py (EDITED for cellinux-bot additional)

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