Advertisement
Kakkoiikun

[Chatango Bot Library] ch.py

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