Pastebin launched a little side project called HostCabi.net, check it out ;-)Don't like ads? PRO users don't see any ads ;-)

ch.py

By: SlackerFS on Jan 16th, 2014  |  syntax: Python  |  size: 52.56 KB  |  hits: 43  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. ################################################################
  2. # File: ch.py
  3. # Title: Chatango Library
  4. # Author: Lumirayz/Lumz <[email protected]>
  5. # Version: 1.3.4a
  6. # Description:
  7. #  An event-based library for connecting to one or multiple Chatango rooms, has
  8. #  support for several things including: messaging, message font,
  9. #  name color, deleting, banning, recent history, 2 userlist modes,
  10. #  flagging, avoiding flood bans, detecting flags.
  11. ################################################################
  12.  
  13. ################################################################
  14. # License
  15. ################################################################
  16. # Copyright 2011 Lumirayz
  17. # This program is distributed under the terms of the GNU GPL.
  18.  
  19. ################################################################
  20. # Imports
  21. ################################################################
  22. import socket
  23. import threading
  24. import time
  25. import random
  26. import re
  27. import sys
  28. import select
  29.  
  30.  
  31. ################################################################
  32. # Python 2 compatibility
  33. ################################################################
  34. if sys.version_info[0] < 3:
  35.   class urllib:
  36.     parse = __import__("urllib")
  37.     request = __import__("urllib2")
  38.   input = raw_input
  39.   import codecs
  40. else:
  41.   import urllib.request
  42.   import urllib.parse
  43.  
  44. ################################################################
  45. # Constants
  46. ################################################################
  47. Userlist_Recent = 0
  48. Userlist_All = 1
  49.  
  50. BigMessage_Multiple = 0
  51. BigMessage_Cut = 1
  52.  
  53. ################################################################
  54. # Struct class
  55. ################################################################
  56. class Struct:
  57.   def __init__(self, **entries):
  58.     self.__dict__.update(entries)
  59.  
  60. ################################################################
  61. # Tagserver stuff
  62. ################################################################
  63. 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}
  64. 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]]
  65.  
  66. def getServer(group):
  67.   """
  68.  Get the server host for a certain room.
  69.  
  70.  @type group: str
  71.  @param group: room name
  72.  
  73.  @rtype: str
  74.  @return: the server's hostname
  75.  """
  76.   try:
  77.     sn = specials[group]
  78.   except KeyError:
  79.     group = group.replace("_", "q")
  80.     group = group.replace("-", "q")
  81.     fnv = float(int(group[0:min(5, len(group))], 36))
  82.     lnv = group[6: (6 + min(3, len(group) - 5))]
  83.     if(lnv):
  84.       lnv = float(int(lnv, 36))
  85.       lnv = max(lnv,1000)
  86.     else:
  87.       lnv = 1000
  88.     num = (fnv % lnv) / lnv
  89.     maxnum = sum(map(lambda x: x[1], tsweights))
  90.     cumfreq = 0
  91.     sn = 0
  92.     for wgt in tsweights:
  93.       cumfreq += float(wgt[1]) / maxnum
  94.       if(num <= cumfreq):
  95.         sn = int(wgt[0])
  96.         break
  97.   return "s" + str(sn) + ".chatango.com"
  98.  
  99. ################################################################
  100. # Uid
  101. ################################################################
  102. def genUid():
  103.   return str(random.randrange(10 ** 15, 10 ** 16))
  104.  
  105. ################################################################
  106. # Message stuff
  107. ################################################################
  108. def clean_message(msg):
  109.   """
  110.  Clean a message and return the message, n tag and f tag.
  111.  
  112.  @type msg: str
  113.  @param msg: the message
  114.  
  115.  @rtype: str, str, str
  116.  @returns: cleaned message, n tag contents, f tag contents
  117.  """
  118.   n = re.search("<n(.*?)/>", msg)
  119.   if n: n = n.group(1)
  120.   f = re.search("<f(.*?)>", msg)
  121.   if f: f = f.group(1)
  122.   msg = re.sub("<n.*?/>", "", msg)
  123.   msg = re.sub("<f.*?>", "", msg)
  124.   msg = strip_html(msg)
  125.   msg = msg.replace("&lt;", "<")
  126.   msg = msg.replace("&gt;", ">")
  127.   msg = msg.replace("&quot;", "\"")
  128.   msg = msg.replace("&apos;", "'")
  129.   msg = msg.replace("&amp;", "&")
  130.   return msg, n, f
  131.  
  132. def strip_html(msg):
  133.   """Strip HTML."""
  134.   li = msg.split("<")
  135.   if len(li) == 1:
  136.     return li[0]
  137.   else:
  138.     ret = list()
  139.     for data in li:
  140.       data = data.split(">", 1)
  141.       if len(data) == 1:
  142.         ret.append(data[0])
  143.       elif len(data) == 2:
  144.         ret.append(data[1])
  145.     return "".join(ret)
  146.  
  147. def parseNameColor(n):
  148.   """This just returns its argument, should return the name color."""
  149.   #probably is already the name
  150.   return n
  151.  
  152. def parseFont(f):
  153.   """Parses the contents of a f tag and returns color, face and size."""
  154.   #' xSZCOL="FONT"'
  155.   try: #TODO: remove quick hack
  156.     sizecolor, fontface = f.split("=", 1)
  157.     sizecolor = sizecolor.strip()
  158.     size = int(sizecolor[1:3])
  159.     col = sizecolor[3:6]
  160.     if col == "": col = None
  161.     face = f.split("\"", 2)[1]
  162.     return col, face, size
  163.   except:
  164.     return None, None, None
  165.  
  166. ################################################################
  167. # Anon id
  168. ################################################################
  169. def getAnonId(n, ssid):
  170.   """Gets the anon's id."""
  171.   if n == None: n = "5504"
  172.   try:
  173.     return "".join(list(
  174.       map(lambda x: str(x[0] + x[1])[-1], list(zip(
  175.         list(map(lambda x: int(x), n)),
  176.         list(map(lambda x: int(x), ssid[4:]))
  177.       )))
  178.     ))
  179.   except ValueError:
  180.     return "NNNN"
  181.  
  182. ################################################################
  183. # PM Auth
  184. ################################################################
  185. auth_re = re.compile(r"auth\.chatango\.com ?= ?([^;]*)", re.IGNORECASE)
  186.  
  187. def _getAuth(name, password):
  188.   """
  189.  Request an auid using name and password.
  190.  
  191.  @type name: str
  192.  @param name: name
  193.  @type password: str
  194.  @param password: password
  195.  
  196.  @rtype: str
  197.  @return: auid
  198.  """
  199.   data = urllib.parse.urlencode({
  200.     "user_id": name,
  201.     "password": password,
  202.     "storecookie": "on",
  203.     "checkerrors": "yes"
  204.   }).encode()
  205.   try:
  206.     resp = urllib.request.urlopen("http://chatango.com/login", data)
  207.     headers = resp.headers
  208.   except Exception:
  209.     return None
  210.   for header, value in headers.items():
  211.     if header.lower() == "set-cookie":
  212.       m = auth_re.search(value)
  213.       if m:
  214.         auth = m.group(1)
  215.         if auth == "":
  216.           return None
  217.         return auth
  218.   return None
  219.  
  220. ################################################################
  221. # PM class
  222. ################################################################
  223. class PM:
  224.   """Manages a connection with Chatango PM."""
  225.   ####
  226.   # Init
  227.   ####
  228.   def __init__(self, mgr):
  229.     self._connected = False
  230.     self._mgr = mgr
  231.     self._auid = None
  232.     self._blocklist = set()
  233.     self._contacts = set()
  234.     self._wlock = False
  235.     self._firstCommand = True
  236.     self._wbuf = b""
  237.     self._wlockbuf = b""
  238.     self._rbuf = b""
  239.     self._pingTask = None
  240.     self._connect()
  241.     if sys.version_info[0] < 3 and sys.platform.startswith("win"):
  242.       self.unicodeCompat = False
  243.     else:
  244.       self.unicodeCompat = True
  245.  
  246.   ####
  247.   # Connections
  248.   ####
  249.   def _connect(self):
  250.     self._wbuf = b""
  251.     self._sock = socket.socket()
  252.     self._sock.connect((self._mgr._PMHost, self._mgr._PMPort))
  253.     self._sock.setblocking(False)
  254.     self._firstCommand = True
  255.     if not self._auth(): return
  256.     self._pingTask = self.mgr.setInterval(self._mgr._pingDelay, self.ping)
  257.     self._connected = True
  258.  
  259.   def _auth(self):
  260.     self._auid = _getAuth(self._mgr.name, self._mgr.password)
  261.     if self._auid == None:
  262.       self._sock.close()
  263.       self._callEvent("onLoginFail")
  264.       self._sock = None
  265.       return False
  266.     self._sendCommand("tlogin", self._auid, "2")
  267.     self._setWriteLock(True)
  268.     return True
  269.  
  270.   def disconnect(self):
  271.     self._disconnect()
  272.     self._callEvent("onPMDisconnect")
  273.  
  274.   def _disconnect(self):
  275.     self._connected = False
  276.     self._sock.close()
  277.     self._sock = None
  278.  
  279.   ####
  280.   # Feed
  281.   ####
  282.   def _feed(self, data):
  283.     """
  284.    Feed data to the connection.
  285.  
  286.    @type data: bytes
  287.    @param data: data to be fed
  288.    """
  289.     self._rbuf += data
  290.     while self._rbuf.find(b"\x00") != -1:
  291.       data = self._rbuf.split(b"\x00")
  292.       for food in data[:-1]:
  293.         if self.unicodeCompat:
  294.           self._process(food.decode().rstrip("\r\n"))
  295.         else:
  296.           self._process(food.decode(errors="replace").rstrip("\r\n"))
  297.       self._rbuf = data[-1]
  298.  
  299.   def _process(self, data):
  300.     """
  301.    Process a command string.
  302.  
  303.    @type data: str
  304.    @param data: the command string
  305.    """
  306.     self._callEvent("onRaw", data)
  307.     data = data.split(":")
  308.     cmd, args = data[0], data[1:]
  309.     func = "rcmd_" + cmd
  310.     if hasattr(self, func):
  311.       getattr(self, func)(args)
  312.  
  313.   ####
  314.   # Properties
  315.   ####
  316.   def getManager(self): return self._mgr
  317.   def getContacts(self): return self._contacts
  318.   def getBlocklist(self): return self._blocklist
  319.  
  320.   mgr = property(getManager)
  321.   contacts = property(getContacts)
  322.   blocklist = property(getBlocklist)
  323.  
  324.   ####
  325.   # Received Commands
  326.   ####
  327.   def rcmd_OK(self, args):
  328.     self._setWriteLock(False)
  329.     self._sendCommand("wl")
  330.     self._sendCommand("getblock")
  331.     self._callEvent("onPMConnect")
  332.  
  333.   def rcmd_wl(self, args):
  334.     self._contacts = set()
  335.     for i in range(len(args) // 4):
  336.       name, last_on, is_on, idle = args[i * 4: i * 4 + 4]
  337.       user = User(name)
  338.       self._contacts.add(user)
  339.     self._callEvent("onPMContactlistReceive")
  340.  
  341.   def rcmd_block_list(self, args):
  342.     self._blocklist = set()
  343.     for name in args:
  344.       if name == "": continue
  345.       self._blocklist.add(User(name))
  346.  
  347.   def rcmd_DENIED(self, args):
  348.     self._disconnect()
  349.     self._callEvent("onLoginFail")
  350.  
  351.   def rcmd_msg(self, args):
  352.     user = User(args[0])
  353.     body = strip_html(":".join(args[5:])) if self.unicodeCompat else  strip_html(":".join(args[5:])) .encode("ascii","ignore").decode("ascii")
  354.     self._callEvent("onPMMessage", user, body)
  355.  
  356.   def rcmd_msgoff(self, args):
  357.     user = User(args[0])
  358.     body = strip_html(":".join(args[5:]))
  359.     self._callEvent("onPMOfflineMessage", user, body)
  360.  
  361.   def rcmd_wlonline(self, args):
  362.     self._callEvent("onPMContactOnline", User(args[0]))
  363.  
  364.   def rcmd_wloffline(self, args):
  365.     self._callEvent("onPMContactOffline", User(args[0]))
  366.  
  367.   def rcmd_kickingoff(self, args):
  368.     self.disconnect()
  369.  
  370.   ####
  371.   # Commands
  372.   ####
  373.   def ping(self):
  374.     self._sendCommand("")
  375.     self._callEvent("onPMPing")
  376.  
  377.   def message(self, user, msg):
  378.     if msg!=None:
  379.       self._sendCommand("msg", user.name, msg)
  380.  
  381.   def addContact(self, user):
  382.     if user not in self._contacts:
  383.       self._sendCommand("wladd", user.name)
  384.       self._contacts.add(user)
  385.       self._callEvent("onPMContactAdd", user)
  386.  
  387.   def removeContact(self, user):
  388.     if user in self._contacts:
  389.       self._sendCommand("wldelete", user.name)
  390.       self._contacts.remove(user)
  391.       self._callEvent("onPMContactRemove", user)
  392.  
  393.   def block(self, user):
  394.     if user not in self._blocklist:
  395.       self._sendCommand("block", user.name)
  396.       self._block.remove(user)
  397.       self._callEvent("onPMBlock", user)
  398.  
  399.   def unblock(self, user):
  400.     if user in self._blocklist:
  401.       self._sendCommand("unblock", user.name)
  402.       self._block.remove(user)
  403.       self._callEvent("onPMUnblock", user)
  404.  
  405.   ####
  406.   # Util
  407.   ####
  408.   def _callEvent(self, evt, *args, **kw):
  409.     getattr(self.mgr, evt)(self, *args, **kw)
  410.     self.mgr.onEventCalled(self, evt, *args, **kw)
  411.  
  412.   def _write(self, data):
  413.     if self._wlock:
  414.       self._wlockbuf += data
  415.     else:
  416.       self.mgr._write(self, data)
  417.  
  418.   def _setWriteLock(self, lock):
  419.     self._wlock = lock
  420.     if self._wlock == False:
  421.       self._write(self._wlockbuf)
  422.       self._wlockbuf = b""
  423.  
  424.   def _sendCommand(self, *args):
  425.     """
  426.    Send a command.
  427.  
  428.    @type args: [str, str, ...]
  429.    @param args: command and list of arguments
  430.    """
  431.     if self._firstCommand:
  432.       terminator = b"\x00"
  433.       self._firstCommand = False
  434.     else:
  435.       terminator = b"\r\n\x00"
  436.     self._write(":".join(args).encode() + terminator)
  437.  
  438. ################################################################
  439. # Room class
  440. ################################################################
  441. class Room:
  442.   """Manages a connection with a Chatango room."""
  443.   ####
  444.   # Init
  445.   ####
  446.   def __init__(self, room, uid = None, server = None, port = None, mgr = None):
  447.     # Basic stuff
  448.     self._name = room
  449.     self._server = server or getServer(room)
  450.     self._port = port or 443
  451.     self._mgr = mgr
  452.  
  453.     # Under the hood
  454.     self._connected = False
  455.     self._reconnecting = False
  456.     self._uid = uid or genUid()
  457.     self._rbuf = b""
  458.     self._wbuf = b""
  459.     self._wlockbuf = b""
  460.     self._owner = None
  461.     self._mods = set()
  462.     self._mqueue = dict()
  463.     self._history = list()
  464.     self._userlist = list()
  465.     self._firstCommand = True
  466.     self._connectAmmount = 0
  467.     self._premium = False
  468.     self._userCount = 0
  469.     self._pingTask = None
  470.     self._botname = None
  471.     self._currentname = None
  472.     self._users = dict()
  473.     self._msgs = dict()
  474.     self._wlock = False
  475.     self._silent = False
  476.     self._banlist = list()
  477.     if sys.version_info[0] < 3 and sys.platform.startswith("win"):
  478.       self.unicodeCompat = False
  479.     else:
  480.       self.unicodeCompat = True
  481.  
  482.     # Inited vars
  483.     if self._mgr: self._connect()
  484.  
  485.   ####
  486.   # User and Message management
  487.   ####
  488.   def getMessage(self, mid):
  489.     return self._msgs.get(mid)
  490.  
  491.   def createMessage(self, msgid, **kw):
  492.     if msgid not in self._msgs:
  493.       msg = Message(msgid = msgid, **kw)
  494.       self._msgs[msgid] = msg
  495.     else:
  496.       msg = self._msgs[msgid]
  497.     return msg
  498.  
  499.   ####
  500.   # Connect/disconnect
  501.   ####
  502.   def _connect(self):
  503.     """Connect to the server."""
  504.     self._sock = socket.socket()
  505.     self._sock.connect((self._server, self._port))
  506.     self._sock.setblocking(False)
  507.     self._firstCommand = True
  508.     self._wbuf = b""
  509.     self._auth()
  510.     self._pingTask = self.mgr.setInterval(self.mgr._pingDelay, self.ping)
  511.     if not self._reconnecting: self.connected = True
  512.  
  513.   def reconnect(self):
  514.     """Reconnect."""
  515.     self._reconnect()
  516.  
  517.   def _reconnect(self):
  518.     """Reconnect."""
  519.     self._reconnecting = True
  520.     if self.connected:
  521.       self._disconnect()
  522.     self._uid = genUid()
  523.     self._connect()
  524.     self._reconnecting = False
  525.  
  526.   def disconnect(self):
  527.     """Disconnect."""
  528.     self._disconnect()
  529.     self._callEvent("onDisconnect")
  530.  
  531.   def _disconnect(self):
  532.     """Disconnect from the server."""
  533.     if not self._reconnecting: self.connected = False
  534.     for user in self._userlist:
  535.       user.clearSessionIds(self)
  536.     self._userlist = list()
  537.     self._pingTask.cancel()
  538.     self._sock.close()
  539.     if not self._reconnecting: del self.mgr._rooms[self.name]
  540.  
  541.   def _auth(self):
  542.     """Authenticate."""
  543.     # login as name with password
  544.     if self.mgr.name and self.mgr.password:
  545.       self._sendCommand("bauth", self.name, self._uid, self.mgr.name, self.mgr.password)
  546.       self._currentname = self.mgr.name
  547.     # login as anon
  548.     else:
  549.       self._sendCommand("bauth", self.name)
  550.  
  551.     self._setWriteLock(True)
  552.  
  553.   ####
  554.   # Properties
  555.   ####
  556.   def getName(self): return self._name
  557.   def getBotName(self):
  558.     if self.mgr.name and self.mgr.password:
  559.       return self.mgr.name
  560.     elif self.mgr.name and self.mgr.password == None:
  561.       return "#" + self.mgr.name
  562.     elif self.mgr.name == None:
  563.       return self._botname
  564.   def getCurrentname(self): return self._currentname
  565.   def getManager(self): return self._mgr
  566.   def getUserlist(self, mode = None, unique = None, memory = None):
  567.     ul = None
  568.     if mode == None: mode = self.mgr._userlistMode
  569.     if unique == None: unique = self.mgr._userlistUnique
  570.     if memory == None: memory = self.mgr._userlistMemory
  571.     if mode == Userlist_Recent:
  572.       ul = map(lambda x: x.user, self._history[-memory:])
  573.     elif mode == Userlist_All:
  574.       ul = self._userlist
  575.     if unique:
  576.       return list(set(ul))
  577.     else:
  578.       return ul
  579.   def getUserNames(self):
  580.     ul = self.userlist
  581.     return list(map(lambda x: x.name, ul))
  582.   def getUser(self): return self.mgr.user
  583.   def getOwner(self): return self._owner
  584.   def getOwnerName(self): return self._owner.name
  585.   def getMods(self):
  586.     newset = set()
  587.     for mod in self._mods:
  588.       newset.add(mod)
  589.     return newset
  590.   def getModNames(self):
  591.     mods = self.getMods()
  592.     return [x.name for x in mods]
  593.   def getUserCount(self): return self._userCount
  594.   def getSilent(self): return self._silent
  595.   def setSilent(self, val): self._silent = val
  596.   def getBanlist(self): return [record[2] for record in self._banlist]
  597.  
  598.   name = property(getName)
  599.   botname = property(getBotName)
  600.   currentname = property(getCurrentname)
  601.   mgr = property(getManager)
  602.   userlist = property(getUserlist)
  603.   usernames = property(getUserNames)
  604.   user = property(getUser)
  605.   owner = property(getOwner)
  606.   ownername = property(getOwnerName)
  607.   mods = property(getMods)
  608.   modnames = property(getModNames)
  609.   usercount = property(getUserCount)
  610.   silent = property(getSilent, setSilent)
  611.   banlist = property(getBanlist)
  612.  
  613.   ####
  614.   # Feed/process
  615.   ####
  616.   def _feed(self, data):
  617.     """
  618.    Feed data to the connection.
  619.  
  620.    @type data: bytes
  621.    @param data: data to be fed
  622.    """
  623.     self._rbuf += data
  624.     while self._rbuf.find(b"\x00") != -1:
  625.       data = self._rbuf.split(b"\x00")
  626.       for food in data[:-1]:
  627.         if self.unicodeCompat:
  628.           self._process(food.decode().rstrip("\r\n"))
  629.         else:
  630.           self._process(food.decode(errors="replace").rstrip("\r\n"))
  631.       self._rbuf = data[-1]
  632.  
  633.   def _process(self, data):
  634.     """
  635.    Process a command string.
  636.    
  637.    @type data: str
  638.    @param data: the command string
  639.    """
  640.     self._callEvent("onRaw", data)
  641.     data = data.split(":")
  642.     cmd, args = data[0], data[1:]
  643.     func = "rcmd_" + cmd
  644.     if hasattr(self, func):
  645.       getattr(self, func)(args)
  646.  
  647.   ####
  648.   # Received Commands
  649.   ####
  650.   def rcmd_ok(self, args):
  651.     # if no name, join room as anon and no password
  652.     if args[2] == "N" and self.mgr.password == None and self.mgr.name == None:
  653.       n = args[4].rsplit('.', 1)[0]
  654.       n = n[-4:]
  655.       aid = args[1][0:8]
  656.       pid = "!anon" + getAnonId(n, aid)
  657.       self._botname = pid
  658.       self._currentname = pid
  659.       self.user._nameColor = n
  660.     # if got name, join room as name and no password
  661.     elif args[2] == "N" and self.mgr.password == None:
  662.       self._sendCommand("blogin", self.mgr.name)
  663.       self._currentname = self.mgr.name
  664.     # if got password but fail to login
  665.     elif args[2] != "M": #unsuccesful login
  666.       self._callEvent("onLoginFail")
  667.       self.disconnect()
  668.     self._owner = User(args[0])
  669.     self._uid = args[1]
  670.     self._aid = args[1][4:8]
  671.     self._mods = set(map(lambda x: User(x), args[6].split(";")))
  672.     self._i_log = list()
  673.  
  674.   def rcmd_denied(self, args):
  675.     self._disconnect()
  676.     self._callEvent("onConnectFail")
  677.     self._callEvent("onStartJoin", "denied")
  678.  
  679.   def rcmd_inited(self, args):
  680.     self._sendCommand("g_participants", "start")
  681.     self._sendCommand("getpremium", "1")
  682.     self.requestBanlist()
  683.     if self._connectAmmount == 0:
  684.       self._callEvent("onConnect")
  685.       for msg in reversed(self._i_log):
  686.         user = msg.user
  687.         self._callEvent("onHistoryMessage", user, msg)
  688.         self._addHistory(msg)
  689.       del self._i_log
  690.       self._callEvent("onStartJoin", "ok")
  691.     else:
  692.       self._callEvent("onReconnect")
  693.     self._connectAmmount += 1
  694.     self._setWriteLock(False)
  695.  
  696.   def rcmd_premium(self, args):
  697.     if float(args[1]) > time.time():
  698.       self._premium = True
  699.       if self.user._mbg: self.setBgMode(1)
  700.       if self.user._mrec: self.setRecordingMode(1)
  701.     else:
  702.       self._premium = False
  703.  
  704.   def rcmd_mods(self, args):
  705.     modnames = args
  706.     mods = set(map(lambda x: User(x), modnames))
  707.     premods = self._mods
  708.     for user in mods - premods: #modded
  709.       self._mods.add(user)
  710.       self._callEvent("onModAdd", user)
  711.     for user in premods - mods: #demodded
  712.       self._mods.remove(user)
  713.       self._callEvent("onModRemove", user)
  714.     self._callEvent("onModChange")
  715.  
  716.   def rcmd_b(self, args):
  717.     mtime = float(args[0])
  718.     puid = args[3]
  719.     ip = args[6]
  720.     name = args[1]
  721.     rawmsg = ":".join(args[9:]) if self.unicodeCompat else ":".join(args[9:]).encode("ascii","ignore").decode("ascii")
  722.     msg, n, f = clean_message(rawmsg)
  723.     if name == "":
  724.       nameColor = None
  725.       name = "#" + args[2]
  726.       if name == "#":
  727.         name = "!anon" + getAnonId(n, puid)
  728.     else:
  729.       if n: nameColor = parseNameColor(n)
  730.       else: nameColor = None
  731.     i = args[5]
  732.     unid = args[4]
  733.     #Create an anonymous message and queue it because msgid is unknown.
  734.     if f: fontColor, fontFace, fontSize = parseFont(f)
  735.     else: fontColor, fontFace, fontSize = None, None, None
  736.     msg = Message(
  737.       time = mtime,
  738.       user = User(name),
  739.       body = msg,
  740.       raw = rawmsg,
  741.       ip = ip,
  742.       nameColor = nameColor,
  743.       fontColor = fontColor,
  744.       fontFace = fontFace,
  745.       fontSize = fontSize,
  746.       unid = unid,
  747.       room = self
  748.     )
  749.     self._mqueue[i] = msg
  750.  
  751.   def rcmd_u(self, args):
  752.     temp = Struct(**self._mqueue)
  753.     if hasattr(temp, args[0]):
  754.       msg = getattr(temp, args[0])
  755.       if msg.user != self.user:
  756.         msg.user._fontColor = msg.fontColor
  757.         msg.user._fontFace = msg.fontFace
  758.         msg.user._fontSize = msg.fontSize
  759.         msg.user._nameColor = msg.nameColor
  760.       del self._mqueue[args[0]]
  761.       msg.attach(self, args[1])
  762.       self._addHistory(msg)
  763.       self._callEvent("onMessage", msg.user, msg)
  764.  
  765.   def rcmd_i(self, args):
  766.     mtime = float(args[0])
  767.     puid = args[3]
  768.     ip = args[6]
  769.     if ip == "": ip = None
  770.     name = args[1]
  771.     rawmsg = ":".join(args[8:])
  772.     msg, n, f = clean_message(rawmsg)
  773.     msgid = args[5]
  774.     if name == "":
  775.       nameColor = None
  776.       name = "#" + args[2]
  777.       if name == "#":
  778.         name = "!anon" + getAnonId(n, puid)
  779.     else:
  780.       if n: nameColor = parseNameColor(n)
  781.       else: nameColor = None
  782.     if f: fontColor, fontFace, fontSize = parseFont(f)
  783.     else: fontColor, fontFace, fontSize = None, None, None
  784.     msg = self.createMessage(
  785.       msgid = msgid,
  786.       time = mtime,
  787.       user = User(name),
  788.       body = msg,
  789.       raw = rawmsg,
  790.       ip = args[6],
  791.       unid = args[4],
  792.       nameColor = nameColor,
  793.       fontColor = fontColor,
  794.       fontFace = fontFace,
  795.       fontSize = fontSize,
  796.       room = self
  797.     )
  798.     if msg.user != self.user:
  799.       msg.user._fontColor = msg.fontColor
  800.       msg.user._fontFace = msg.fontFace
  801.       msg.user._fontSize = msg.fontSize
  802.       msg.user._nameColor = msg.nameColor
  803.     self._i_log.append(msg)
  804.  
  805.   def rcmd_g_participants(self, args):
  806.     args = ":".join(args)
  807.     args = args.split(";")
  808.     for data in args:
  809.       data = data.split(":")
  810.       name = data[3].lower()
  811.       if name == "none": continue
  812.       user = User(
  813.         name = name,
  814.         room = self
  815.       )
  816.       user.addSessionId(self, data[0])
  817.       self._userlist.append(user)
  818.  
  819.   def rcmd_participant(self, args):
  820.     if args[0] == "0": #leave
  821.       name = args[3].lower()
  822.       if name == "none": return
  823.       user = User(name)
  824.       user.removeSessionId(self, args[1])
  825.       self._userlist.remove(user)
  826.       if user not in self._userlist or not self.mgr._userlistEventUnique:
  827.         self._callEvent("onLeave", user)
  828.     else: #join
  829.       name = args[3].lower()
  830.       if name == "none": return
  831.       user = User(
  832.         name = name,
  833.         room = self
  834.       )
  835.       user.addSessionId(self, args[1])
  836.       if user not in self._userlist: doEvent = True
  837.       else: doEvent = False
  838.       self._userlist.append(user)
  839.       if doEvent or not self.mgr._userlistEventUnique:
  840.         self._callEvent("onJoin", user)
  841.  
  842.   def rcmd_show_fw(self, args):
  843.     self._callEvent("onFloodWarning")
  844.  
  845.   def rcmd_show_tb(self, args):
  846.     self._callEvent("onFloodBan")
  847.  
  848.   def rcmd_tb(self, args):
  849.     self._callEvent("onFloodBanRepeat")
  850.  
  851.   def rcmd_delete(self, args):
  852.     msg = self.getMessage(args[0])
  853.     if msg:
  854.       if msg in self._history:
  855.         self._history.remove(msg)
  856.         self._callEvent("onMessageDelete", msg.user, msg)
  857.         msg.detach()
  858.  
  859.   def rcmd_deleteall(self, args):
  860.     for msgid in args:
  861.       self.rcmd_delete([msgid])
  862.  
  863.   def rcmd_n(self, args):
  864.     self._userCount = int(args[0], 16)
  865.     self._callEvent("onUserCountChange")
  866.  
  867.   def rcmd_blocklist(self, args):
  868.     self._banlist = list()
  869.     sections = ":".join(args).split(";")
  870.     for section in sections:
  871.       params = section.split(":")
  872.       if len(params) != 5: continue
  873.       if params[2] == "": continue
  874.       self._banlist.append((
  875.         params[0], #unid
  876.         params[1], #ip
  877.         User(params[2]), #target
  878.         float(params[3]), #time
  879.         User(params[4]) #src
  880.       ))
  881.     self._callEvent("onBanlistUpdate")
  882.  
  883.   def rcmd_blocked(self, args):
  884.     if args[2] == "": return
  885.     target = User(args[2])
  886.     user = User(args[3])
  887.     self._banlist.append((args[0], args[1], target, float(args[4]), user))
  888.     self._callEvent("onBan", user, target)
  889.     self.requestBanlist()
  890.  
  891.   def rcmd_unblocked(self, args):
  892.     if args[2] == "": return
  893.     target = User(args[2])
  894.     self._callEvent("onUnban", user, target)
  895.     self.requestBanlist()
  896.  
  897.   ####
  898.   # Commands
  899.   ####
  900.   def login(self, NAME, PASS = None):
  901.     if PASS:
  902.       self._sendCommand("blogin", NAME, PASS)
  903.     else:
  904.       self._sendCommand("blogin", NAME)
  905.     self._currentname = NAME
  906.  
  907.   def logout(self):
  908.     self._sendCommand("blogout")
  909.     self._currentname = self._botname
  910.  
  911.   def ping(self):
  912.     """Send a ping."""
  913.     self._sendCommand("")
  914.     self._callEvent("onPing")
  915.  
  916.   def rawMessage(self, msg):
  917.     """
  918.    Send a message without n and f tags.
  919.  
  920.    @type msg: str
  921.    @param msg: message
  922.    """
  923.     if not self._silent:
  924.       self._sendCommand("bmsg:tl2r", msg)
  925.  
  926.   def message(self, msg, html = False):
  927.     """
  928.    Send a message.
  929.  
  930.    @type msg: str
  931.    @param msg: message
  932.    """
  933.     if msg==None:
  934.       return
  935.     msg = msg.rstrip()
  936.     if not html:
  937.       msg = msg.replace("<", "&lt;").replace(">", "&gt;")
  938.     if len(msg) > self.mgr._maxLength:
  939.       if self.mgr._tooBigMessage == BigMessage_Cut:
  940.         self.message(msg[:self.mgr._maxLength], html = html)
  941.       elif self.mgr._tooBigMessage == BigMessage_Multiple:
  942.         while len(msg) > 0:
  943.           sect = msg[:self.mgr._maxLength]
  944.           msg = msg[self.mgr._maxLength:]
  945.           self.message(sect, html = html)
  946.       return
  947.     msg = "<n" + self.user.nameColor + "/>" + msg
  948.     if self._currentname != None or not self._currentname.startswith("!anon"):
  949.       msg = "<f x%0.2i%s=\"%s\">" %(self.user.fontSize, self.user.fontColor, self.user.fontFace) + msg
  950.     self.rawMessage(msg)
  951.  
  952.   def setBgMode(self, mode):
  953.     self._sendCommand("msgbg", str(mode))
  954.  
  955.   def setRecordingMode(self, mode):
  956.     self._sendCommand("msgmedia", str(mode))
  957.  
  958.   def addMod(self, user):
  959.     """
  960.    Add a moderator.
  961.  
  962.    @type user: User
  963.    @param user: User to mod.
  964.    """
  965.     if self.getLevel(self.user) == 2:
  966.       self._sendCommand("addmod", user.name)
  967.  
  968.   def removeMod(self, user):
  969.     """
  970.    Remove a moderator.
  971.  
  972.    @type user: User
  973.    @param user: User to demod.
  974.    """
  975.     if self.getLevel(self.user) == 2:
  976.       self._sendCommand("removemod", user.name)
  977.  
  978.   def flag(self, message):
  979.     """
  980.    Flag a message.
  981.  
  982.    @type message: Message
  983.    @param message: message to flag
  984.    """
  985.     self._sendCommand("g_flag", message.msgid)
  986.  
  987.   def flagUser(self, user):
  988.     """
  989.    Flag a user.
  990.  
  991.    @type user: User
  992.    @param user: user to flag
  993.  
  994.    @rtype: bool
  995.    @return: whether a message to flag was found
  996.    """
  997.     msg = self.getLastMessage(user)
  998.     if msg:
  999.       self.flag(msg)
  1000.       return True
  1001.     return False
  1002.  
  1003.   def delete(self, message):
  1004.     """
  1005.    Delete a message. (Moderator only)
  1006.  
  1007.    @type message: Message
  1008.    @param message: message to delete
  1009.    """
  1010.     if self.getLevel(self.user) > 0:
  1011.       self._sendCommand("delmsg", message.msgid)
  1012.  
  1013.   def rawClearUser(self, unid):
  1014.     self._sendCommand("delallmsg", unid)
  1015.  
  1016.   def clearUser(self, user):
  1017.     """
  1018.    Clear all of a user's messages. (Moderator only)
  1019.  
  1020.    @type user: User
  1021.    @param user: user to delete messages of
  1022.  
  1023.    @rtype: bool
  1024.    @return: whether a message to delete was found
  1025.    """
  1026.     if self.getLevel(self.user) > 0:
  1027.       msg = self.getLastMessage(user)
  1028.       if msg:
  1029.         self.rawClearUser(msg.unid)
  1030.       return True
  1031.     return False
  1032.  
  1033.   def clearall(self):
  1034.     """Clear all messages. (Owner only)"""
  1035.     if self.getLevel(self.user) == 2:
  1036.       self._sendCommand("clearall")
  1037.  
  1038.   def rawBan(self, name, ip, unid):
  1039.     """
  1040.    Execute the block command using specified arguments.
  1041.    (For advanced usage)
  1042.  
  1043.    @type name: str
  1044.    @param name: name
  1045.    @type ip: str
  1046.    @param ip: ip address
  1047.    @type unid: str
  1048.    @param unid: unid
  1049.    """
  1050.     self._sendCommand("block", unid, ip, name)
  1051.  
  1052.   def ban(self, msg):
  1053.     """
  1054.    Ban a message's sender. (Moderator only)
  1055.  
  1056.    @type message: Message
  1057.    @param message: message to ban sender of
  1058.    """
  1059.     if self.getLevel(self.user) > 0:
  1060.       self.rawBan(msg.user.name, msg.ip, msg.unid)
  1061.  
  1062.   def banUser(self, user):
  1063.     """
  1064.    Ban a user. (Moderator only)
  1065.  
  1066.    @type user: User
  1067.    @param user: user to ban
  1068.  
  1069.    @rtype: bool
  1070.    @return: whether a message to ban the user was found
  1071.    """
  1072.     msg = self.getLastMessage(user)
  1073.     if msg:
  1074.       self.ban(msg)
  1075.       return True
  1076.     return False
  1077.  
  1078.   def requestBanlist(self):
  1079.     """Request an updated banlist."""
  1080.     self._sendCommand("blocklist", "block", "", "next", "500")
  1081.  
  1082.   def rawUnban(self, name, ip, unid):
  1083.     """
  1084.    Execute the unblock command using specified arguments.
  1085.    (For advanced usage)
  1086.  
  1087.    @type name: str
  1088.    @param name: name
  1089.    @type ip: str
  1090.    @param ip: ip address
  1091.    @type unid: str
  1092.    @param unid: unid
  1093.    """
  1094.     self._sendCommand("removeblock", unid, ip, name)
  1095.  
  1096.   def unban(self, user):
  1097.     """
  1098.    Unban a user. (Moderator only)
  1099.  
  1100.    @type user: User
  1101.    @param user: user to unban
  1102.  
  1103.    @rtype: bool
  1104.    @return: whether it succeeded
  1105.    """
  1106.     rec = self._getBanRecord(user)
  1107.     if rec:
  1108.       self.rawUnban(rec[2].name, rec[1], rec[0])
  1109.       return True
  1110.     else:
  1111.       return False
  1112.  
  1113.   ####
  1114.   # Util
  1115.   ####
  1116.   def _getBanRecord(self, user):
  1117.     for record in self._banlist:
  1118.       if record[2] == user:
  1119.         return record
  1120.     return None
  1121.  
  1122.   def _callEvent(self, evt, *args, **kw):
  1123.     getattr(self.mgr, evt)(self, *args, **kw)
  1124.     self.mgr.onEventCalled(self, evt, *args, **kw)
  1125.  
  1126.   def _write(self, data):
  1127.     if self._wlock:
  1128.       self._wlockbuf += data
  1129.     else:
  1130.       self.mgr._write(self, data)
  1131.  
  1132.   def _setWriteLock(self, lock):
  1133.     self._wlock = lock
  1134.     if self._wlock == False:
  1135.       self._write(self._wlockbuf)
  1136.       self._wlockbuf = b""
  1137.  
  1138.   def _sendCommand(self, *args):
  1139.     """
  1140.    Send a command.
  1141.  
  1142.    @type args: [str, str, ...]
  1143.    @param args: command and list of arguments
  1144.    """
  1145.     if self._firstCommand:
  1146.       terminator = b"\x00"
  1147.       self._firstCommand = False
  1148.     else:
  1149.       terminator = b"\r\n\x00"
  1150.     self._write(":".join(args).encode() + terminator)
  1151.  
  1152.   def getLevel(self, user):
  1153.     if user == self._owner: return 2
  1154.     if user in self._mods: return 1
  1155.     return 0
  1156.  
  1157.   def getLastMessage(self, user = None):
  1158.     if user:
  1159.       try:
  1160.         i = 1
  1161.         while True:
  1162.           msg = self._history[-i]
  1163.           if msg.user == user:
  1164.             return msg
  1165.           i += 1
  1166.       except IndexError:
  1167.         return None
  1168.     else:
  1169.       try:
  1170.         return self._history[-1]
  1171.       except IndexError:
  1172.         return None
  1173.     return None
  1174.  
  1175.   def findUser(self, name):
  1176.     name = name.lower()
  1177.     ul = self.getUserlist()
  1178.     udi = dict(zip([u.name for u in ul], ul))
  1179.     cname = None
  1180.     for n in udi.keys():
  1181.       if n.find(name) != -1:
  1182.         if cname: return None #ambiguous!!
  1183.         cname = n
  1184.     if cname: return udi[cname]
  1185.     else: return None
  1186.  
  1187.   ####
  1188.   # History
  1189.   ####
  1190.   def _addHistory(self, msg):
  1191.     """
  1192.    Add a message to history.
  1193.  
  1194.    @type msg: Message
  1195.    @param msg: message
  1196.    """
  1197.     self._history.append(msg)
  1198.     if len(self._history) > self.mgr._maxHistoryLength:
  1199.       rest, self._history = self._history[:-self.mgr._maxHistoryLength], self._history[-self.mgr._maxHistoryLength:]
  1200.       for msg in rest: msg.detach()
  1201.  
  1202. ################################################################
  1203. # RoomManager class
  1204. ################################################################
  1205. class RoomManager:
  1206.   """Class that manages multiple connections."""
  1207.   ####
  1208.   # Config
  1209.   ####
  1210.   _Room = Room
  1211.   _PM = PM
  1212.   _PMHost = "c1.chatango.com"
  1213.   _PMPort = 5222
  1214.   _TimerResolution = 0.2 #at least x times per second
  1215.   _pingDelay = 20
  1216.   _userlistMode = Userlist_Recent
  1217.   _userlistUnique = True
  1218.   _userlistMemory = 50
  1219.   _userlistEventUnique = False
  1220.   _tooBigMessage = BigMessage_Multiple
  1221.   _maxLength = 1800
  1222.   _maxHistoryLength = 150
  1223.  
  1224.   ####
  1225.   # Init
  1226.   ####
  1227.   def __init__(self, name = None, password = None, pm = True):
  1228.     self._name = name
  1229.     self._password = password
  1230.     self._running = False
  1231.     self._tasks = set()
  1232.     self._rooms = dict()
  1233.     self._rooms_copy = list()
  1234.     if pm:
  1235.       self._pm = self._PM(mgr = self)
  1236.     else:
  1237.       self._pm = None
  1238.  
  1239.   ####
  1240.   # Join/leave
  1241.   ####
  1242.   def joinRoom(self, room):
  1243.     """
  1244.    Join a room or return None if already joined.
  1245.  
  1246.    @type room: str
  1247.    @param room: room to join
  1248.  
  1249.    @rtype: Room or None
  1250.    @return: the room or nothing
  1251.    """
  1252.     room = room.lower()
  1253.     if room not in self._rooms:
  1254.       con = self._Room(room, mgr = self)
  1255.       self._rooms[room] = con
  1256.       return con
  1257.     else:
  1258.       return None
  1259.  
  1260.   def leaveRoom(self, room):
  1261.     """
  1262.    Leave a room.
  1263.  
  1264.    @type room: str
  1265.    @param room: room to leave
  1266.    """
  1267.     room = room.lower()
  1268.     if room in self._rooms:
  1269.       con = self._rooms[room]
  1270.       con.disconnect()
  1271.  
  1272.   def getRoom(self, room):
  1273.     """
  1274.    Get room with a name, or None if not connected to this room.
  1275.  
  1276.    @type room: str
  1277.    @param room: room
  1278.  
  1279.    @rtype: Room
  1280.    @return: the room
  1281.    """
  1282.     room = room.lower()
  1283.     if room in self._rooms:
  1284.       return self._rooms[room]
  1285.     else:
  1286.       return None
  1287.  
  1288.   ####
  1289.   # Properties
  1290.   ####
  1291.   def getUser(self): return User(self._name)
  1292.   def getName(self): return self._name
  1293.   def getPassword(self): return self._password
  1294.   def getRooms(self): return set(self._rooms.values())
  1295.   def getRoomNames(self): return set(self._rooms.keys())
  1296.   def getRooms_copy(self): return set(self._rooms_copy)
  1297.   def getPM(self): return self._pm
  1298.  
  1299.   user = property(getUser)
  1300.   name = property(getName)
  1301.   password = property(getPassword)
  1302.   rooms = property(getRooms)
  1303.   roomnames = property(getRoomNames)
  1304.   rooms_copy = property(getRooms_copy)
  1305.   pm = property(getPM)
  1306.  
  1307.   ####
  1308.   # Virtual methods
  1309.   ####
  1310.   def onInit(self):
  1311.     """Called on init."""
  1312.     pass
  1313.  
  1314.   def safePrint(self, text):
  1315.     """ use this to safely print text with unicode"""
  1316.     while True:
  1317.       try:
  1318.         print(text)
  1319.         break
  1320.       except UnicodeEncodeError as ex:
  1321.         text = (text[0:ex.start]+'(unicode)'+text[ex.end:])
  1322.  
  1323.   def onStartJoin(self, room, status):
  1324.     """Don't edit unless you know what you are doing"""
  1325.     if status == "ok":
  1326.       if self._rooms_copy == []: pass
  1327.       elif len(self._rooms_copy) > 0:
  1328.         self.joinRoom(self._rooms_copy.pop())
  1329.  
  1330.     elif status == "denied": # if it fail to connect, skip it
  1331.       if self._rooms_copy == []: pass
  1332.       elif len(self._rooms_copy) > 0:
  1333.         self.joinRoom(self._rooms_copy.pop())
  1334.  
  1335.   def onConnect(self, room):
  1336.     """
  1337.    Called when connected to the room.
  1338.    
  1339.    @type room: Room
  1340.    @param room: room where the event occured
  1341.    """
  1342.     pass
  1343.  
  1344.   def onReconnect(self, room):
  1345.     """
  1346.    Called when reconnected to the room.
  1347.    
  1348.    @type room: Room
  1349.    @param room: room where the event occured
  1350.    """
  1351.     pass
  1352.  
  1353.   def onConnectFail(self, room):
  1354.     """
  1355.    Called when the connection failed.
  1356.    
  1357.    @type room: Room
  1358.    @param room: room where the event occured
  1359.    """
  1360.     pass
  1361.  
  1362.   def onDisconnect(self, room):
  1363.     """
  1364.    Called when the client gets disconnected.
  1365.    
  1366.    @type room: Room
  1367.    @param room: room where the event occured
  1368.    """
  1369.     pass
  1370.  
  1371.   def onLoginFail(self, room):
  1372.     """
  1373.    Called on login failure, disconnects after.
  1374.    
  1375.    @type room: Room
  1376.    @param room: room where the event occured
  1377.    """
  1378.     pass
  1379.  
  1380.   def onFloodBan(self, room):
  1381.     """
  1382.    Called when either flood banned or flagged.
  1383.    
  1384.    @type room: Room
  1385.    @param room: room where the event occured
  1386.    """
  1387.     pass
  1388.  
  1389.   def onFloodBanRepeat(self, room):
  1390.     """
  1391.    Called when trying to send something when floodbanned.
  1392.  
  1393.    @type room: Room
  1394.    @param room: room where the event occured
  1395.    """
  1396.     pass
  1397.  
  1398.   def onFloodWarning(self, room):
  1399.     """
  1400.    Called when an overflow warning gets received.
  1401.  
  1402.    @type room: Room
  1403.    @param room: room where the event occured
  1404.    """
  1405.     pass
  1406.  
  1407.   def onMessageDelete(self, room, user, message):
  1408.     """
  1409.    Called when a message gets deleted.
  1410.  
  1411.    @type room: Room
  1412.    @param room: room where the event occured
  1413.    @type user: User
  1414.    @param user: owner of deleted message
  1415.    @type message: Message
  1416.    @param message: message that got deleted
  1417.    """
  1418.     pass
  1419.  
  1420.   def onModChange(self, room):
  1421.     """
  1422.    Called when the moderator list changes.
  1423.  
  1424.    @type room: Room
  1425.    @param room: room where the event occured
  1426.    """
  1427.     pass
  1428.  
  1429.   def onModAdd(self, room, user):
  1430.     """
  1431.    Called when a moderator gets added.
  1432.  
  1433.    @type room: Room
  1434.    @param room: room where the event occured
  1435.    """
  1436.     pass
  1437.  
  1438.   def onModRemove(self, room, user):
  1439.     """
  1440.    Called when a moderator gets removed.
  1441.  
  1442.    @type room: Room
  1443.    @param room: room where the event occured
  1444.    """
  1445.     pass
  1446.  
  1447.   def onMessage(self, room, user, message):
  1448.     """
  1449.    Called when a message gets received.
  1450.  
  1451.    @type room: Room
  1452.    @param room: room where the event occured
  1453.    @type user: User
  1454.    @param user: owner of message
  1455.    @type message: Message
  1456.    @param message: received message
  1457.    """
  1458.     pass
  1459.  
  1460.   def onHistoryMessage(self, room, user, message):
  1461.     """
  1462.    Called when a message gets received from history.
  1463.  
  1464.    @type room: Room
  1465.    @param room: room where the event occured
  1466.    @type user: User
  1467.    @param user: owner of message
  1468.    @type message: Message
  1469.    @param message: the message that got added
  1470.    """
  1471.     pass
  1472.  
  1473.   def onJoin(self, room, user):
  1474.     """
  1475.    Called when a user joins. Anonymous users get ignored here.
  1476.  
  1477.    @type room: Room
  1478.    @param room: room where the event occured
  1479.    @type user: User
  1480.    @param user: the user that has joined
  1481.    """
  1482.     pass
  1483.  
  1484.   def onLeave(self, room, user):
  1485.     """
  1486.    Called when a user leaves. Anonymous users get ignored here.
  1487.  
  1488.    @type room: Room
  1489.    @param room: room where the event occured
  1490.    @type user: User
  1491.    @param user: the user that has left
  1492.    """
  1493.     pass
  1494.  
  1495.   def onRaw(self, room, raw):
  1496.     """
  1497.    Called before any command parsing occurs.
  1498.  
  1499.    @type room: Room
  1500.    @param room: room where the event occured
  1501.    @type raw: str
  1502.    @param raw: raw command data
  1503.    """
  1504.     pass
  1505.  
  1506.   def onPing(self, room):
  1507.     """
  1508.    Called when a ping gets sent.
  1509.  
  1510.    @type room: Room
  1511.    @param room: room where the event occured
  1512.    """
  1513.     pass
  1514.  
  1515.   def onUserCountChange(self, room):
  1516.     """
  1517.    Called when the user count changes.
  1518.  
  1519.    @type room: Room
  1520.    @param room: room where the event occured
  1521.    """
  1522.     pass
  1523.  
  1524.   def onBan(self, room, user, target):
  1525.     """
  1526.    Called when a user gets banned.
  1527.  
  1528.    @type room: Room
  1529.    @param room: room where the event occured
  1530.    @type user: User
  1531.    @param user: user that banned someone
  1532.    @type target: User
  1533.    @param target: user that got banned
  1534.    """
  1535.     pass
  1536.  
  1537.   def onUnban(self, room, user, target):
  1538.     """
  1539.    Called when a user gets unbanned.
  1540.  
  1541.    @type room: Room
  1542.    @param room: room where the event occured
  1543.    @type user: User
  1544.    @param user: user that unbanned someone
  1545.    @type target: User
  1546.    @param target: user that got unbanned
  1547.    """
  1548.     pass
  1549.  
  1550.   def onBanlistUpdate(self, room):
  1551.     """
  1552.    Called when a banlist gets updated.
  1553.  
  1554.    @type room: Room
  1555.    @param room: room where the event occured
  1556.    """
  1557.     pass
  1558.  
  1559.   def onPMConnect(self, pm):
  1560.     pass
  1561.  
  1562.   def onPMDisconnect(self, pm):
  1563.     pass
  1564.  
  1565.   def onPMPing(self, pm):
  1566.     pass
  1567.  
  1568.   def onPMMessage(self, pm, user, body):
  1569.     pass
  1570.  
  1571.   def onPMOfflineMessage(self, pm, user, body):
  1572.     pass
  1573.  
  1574.   def onPMContactlistReceive(self, pm):
  1575.     pass
  1576.  
  1577.   def onPMBlocklistReceive(self, pm):
  1578.     pass
  1579.  
  1580.   def onPMContactAdd(self, pm, user):
  1581.     pass
  1582.  
  1583.   def onPMContactRemove(self, pm, user):
  1584.     pass
  1585.  
  1586.   def onPMBlock(self, pm, user):
  1587.     pass
  1588.  
  1589.   def onPMUnblock(self, pm, user):
  1590.     pass
  1591.  
  1592.   def onPMContactOnline(self, pm, user):
  1593.     pass
  1594.  
  1595.   def onPMContactOffline(self, pm, user):
  1596.     pass
  1597.  
  1598.   def onEventCalled(self, room, evt, *args, **kw):
  1599.     """
  1600.    Called on every room-based event.
  1601.  
  1602.    @type room: Room
  1603.    @param room: room where the event occured
  1604.    @type evt: str
  1605.    @param evt: the event
  1606.    """
  1607.     pass
  1608.  
  1609.   ####
  1610.   # Deferring
  1611.   ####
  1612.   def deferToThread(self, callback, func, *args, **kw):
  1613.     """
  1614.    Defer a function to a thread and callback the return value.
  1615.  
  1616.    @type callback: function
  1617.    @param callback: function to call on completion
  1618.    @type cbargs: tuple or list
  1619.    @param cbargs: arguments to get supplied to the callback
  1620.    @type func: function
  1621.    @param func: function to call
  1622.    """
  1623.     def f(func, callback, *args, **kw):
  1624.       ret = func(*args, **kw)
  1625.       self.setTimeout(0, callback, ret)
  1626.     threading._start_new_thread(f, (func, callback) + args, kw)
  1627.  
  1628.   ####
  1629.   # Scheduling
  1630.   ####
  1631.   class _Task:
  1632.     def cancel(self):
  1633.       """Sugar for removeTask."""
  1634.       self.mgr.removeTask(self)
  1635.  
  1636.   def _tick(self):
  1637.     now = time.time()
  1638.     for task in set(self._tasks):
  1639.       if task.target <= now:
  1640.         task.func(*task.args, **task.kw)
  1641.         if task.isInterval:
  1642.           task.target = now + task.timeout
  1643.         else:
  1644.           self._tasks.remove(task)
  1645.  
  1646.   def setTimeout(self, timeout, func, *args, **kw):
  1647.     """
  1648.    Call a function after at least timeout seconds with specified arguments.
  1649.  
  1650.    @type timeout: int
  1651.    @param timeout: timeout
  1652.    @type func: function
  1653.    @param func: function to call
  1654.  
  1655.    @rtype: _Task
  1656.    @return: object representing the task
  1657.    """
  1658.     task = self._Task()
  1659.     task.mgr = self
  1660.     task.target = time.time() + timeout
  1661.     task.timeout = timeout
  1662.     task.func = func
  1663.     task.isInterval = False
  1664.     task.args = args
  1665.     task.kw = kw
  1666.     self._tasks.add(task)
  1667.     return task
  1668.  
  1669.   def setInterval(self, timeout, func, *args, **kw):
  1670.     """
  1671.    Call a function at least every timeout seconds with specified arguments.
  1672.  
  1673.    @type timeout: int
  1674.    @param timeout: timeout
  1675.    @type func: function
  1676.    @param func: function to call
  1677.  
  1678.    @rtype: _Task
  1679.    @return: object representing the task
  1680.    """
  1681.     task = self._Task()
  1682.     task.mgr = self
  1683.     task.target = time.time() + timeout
  1684.     task.timeout = timeout
  1685.     task.func = func
  1686.     task.isInterval = True
  1687.     task.args = args
  1688.     task.kw = kw
  1689.     self._tasks.add(task)
  1690.     return task
  1691.  
  1692.   def removeTask(self, task):
  1693.     """
  1694.    Cancel a task.
  1695.  
  1696.    @type task: _Task
  1697.    @param task: task to cancel
  1698.    """
  1699.     self._tasks.remove(task)
  1700.  
  1701.   ####
  1702.   # Util
  1703.   ####
  1704.   def _write(self, room, data):
  1705.     room._wbuf += data
  1706.  
  1707.   def getConnections(self):
  1708.     li = list(self._rooms.values())
  1709.     if self._pm:
  1710.       li.append(self._pm)
  1711.     return [c for c in li if c._sock != None]
  1712.  
  1713.   ####
  1714.   # Main
  1715.   ####
  1716.   def main(self):
  1717.     self.onInit()
  1718.     self._running = True
  1719.     while self._running:
  1720.       conns = self.getConnections()
  1721.       socks = [x._sock for x in conns]
  1722.       wsocks = [x._sock for x in conns if x._wbuf != b""]
  1723.       rd, wr, sp = select.select(socks, wsocks, [], self._TimerResolution)
  1724.       for sock in rd:
  1725.         con = [c for c in conns if c._sock == sock][0]
  1726.         try:
  1727.           data = sock.recv(1024)
  1728.           if(len(data) > 0):
  1729.             con._feed(data)
  1730.           else:
  1731.             con.disconnect()
  1732.         except socket.error:
  1733.           pass
  1734.       for sock in wr:
  1735.         con = [c for c in conns if c._sock == sock][0]
  1736.         try:
  1737.           size = sock.send(con._wbuf)
  1738.           con._wbuf = con._wbuf[size:]
  1739.         except socket.error:
  1740.           pass
  1741.       self._tick()
  1742.  
  1743.   @classmethod
  1744.   def easy_start(cl, rooms = None, name = None, password = None, pm = True):
  1745.     """
  1746.    Prompts the user for missing info, then starts.
  1747.  
  1748.    @type rooms: list
  1749.    @param room: rooms to join
  1750.    @type name: str
  1751.    @param name: name to join as ("" = None, None = unspecified)
  1752.    @type password: str
  1753.    @param password: password to join with ("" = None, None = unspecified)
  1754.    """
  1755.     if not rooms: rooms = str(input("Room names separated by semicolons: ")).split(";")
  1756.     if len(rooms) == 1 and rooms[0] == "": rooms = []
  1757.     if not name: name = str(input("User name: "))
  1758.     if name == "": name = None
  1759.     if not password: password = str(input("User password: "))
  1760.     if password == "": password = None
  1761.     self = cl(name, password, pm = pm)
  1762.     self._rooms_copy=rooms[:]
  1763.     if len(self._rooms_copy)>0:
  1764.         self.joinRoom(self._rooms_copy.pop())
  1765.     self.main()
  1766.  
  1767.   def stop(self):
  1768.     for conn in list(self._rooms.values()):
  1769.       conn.disconnect()
  1770.     self._running = False
  1771.  
  1772.   ####
  1773.   # Commands
  1774.   ####
  1775.   def enableBg(self):
  1776.     """Enable background if available."""
  1777.     self.user._mbg = True
  1778.     for room in self.rooms:
  1779.       room.setBgMode(1)
  1780.  
  1781.   def disableBg(self):
  1782.     """Disable background."""
  1783.     self.user._mbg = False
  1784.     for room in self.rooms:
  1785.       room.setBgMode(0)
  1786.  
  1787.   def enableRecording(self):
  1788.     """Enable recording if available."""
  1789.     self.user._mrec = True
  1790.     for room in self.rooms:
  1791.       room.setRecordingMode(1)
  1792.  
  1793.   def disableRecording(self):
  1794.     """Disable recording."""
  1795.     self.user._mrec = False
  1796.     for room in self.rooms:
  1797.       room.setRecordingMode(0)
  1798.  
  1799.   def setNameColor(self, color3x):
  1800.     """
  1801.    Set name color.
  1802.  
  1803.    @type color3x: str
  1804.    @param color3x: a 3-char RGB hex code for the color
  1805.    """
  1806.     self.user._nameColor = color3x
  1807.  
  1808.   def setFontColor(self, color3x):
  1809.     """
  1810.    Set font color.
  1811.  
  1812.    @type color3x: str
  1813.    @param color3x: a 3-char RGB hex code for the color
  1814.    """
  1815.     self.user._fontColor = color3x
  1816.  
  1817.   def setFontFace(self, face):
  1818.     """
  1819.    Set font face/family.
  1820.  
  1821.    @type face: str
  1822.    @param face: the font face
  1823.    """
  1824.     self.user._fontFace = face
  1825.  
  1826.   def setFontSize(self, size):
  1827.     """
  1828.    Set font size.
  1829.  
  1830.    @type size: int
  1831.    @param size: the font size (limited: 9 to 22)
  1832.    """
  1833.     if size < 9: size = 9
  1834.     if size > 22: size = 22
  1835.     self.user._fontSize = size
  1836.  
  1837. ################################################################
  1838. # User class (well, yeah, I lied, it's actually _User)
  1839. ################################################################
  1840. _users = dict()
  1841. def User(name, *args, **kw):
  1842.   if name == None: name = ""
  1843.   name = name.lower()
  1844.   user = _users.get(name)
  1845.   if not user:
  1846.     user = _User(name = name, *args, **kw)
  1847.     _users[name] = user
  1848.   return user
  1849.  
  1850. class _User:
  1851.   """Class that represents a user."""
  1852.   ####
  1853.   # Init
  1854.   ####
  1855.   def __init__(self, name, **kw):
  1856.     self._name = name.lower()
  1857.     self._sids = dict()
  1858.     self._msgs = list()
  1859.     self._nameColor = "000"
  1860.     self._fontSize = 12
  1861.     self._fontFace = "0"
  1862.     self._fontColor = "000"
  1863.     self._mbg = False
  1864.     self._mrec = False
  1865.     for attr, val in kw.items():
  1866.       if val == None: continue
  1867.       setattr(self, "_" + attr, val)
  1868.  
  1869.   ####
  1870.   # Properties
  1871.   ####
  1872.   def getName(self): return self._name
  1873.   def getSessionIds(self, room = None):
  1874.     if room:
  1875.       return self._sids.get(room, set())
  1876.     else:
  1877.       return set.union(*self._sids.values())
  1878.   def getRooms(self): return self._sids.keys()
  1879.   def getRoomNames(self): return [room.name for room in self.getRooms()]
  1880.   def getFontColor(self): return self._fontColor
  1881.   def getFontFace(self): return self._fontFace
  1882.   def getFontSize(self): return self._fontSize
  1883.   def getNameColor(self): return self._nameColor
  1884.  
  1885.   name = property(getName)
  1886.   sessionids = property(getSessionIds)
  1887.   rooms = property(getRooms)
  1888.   roomnames = property(getRoomNames)
  1889.   fontColor = property(getFontColor)
  1890.   fontFace = property(getFontFace)
  1891.   fontSize = property(getFontSize)
  1892.   nameColor = property(getNameColor)
  1893.  
  1894.   ####
  1895.   # Util
  1896.   ####
  1897.   def addSessionId(self, room, sid):
  1898.     if room not in self._sids:
  1899.       self._sids[room] = set()
  1900.     self._sids[room].add(sid)
  1901.  
  1902.   def removeSessionId(self, room, sid):
  1903.     try:
  1904.       self._sids[room].remove(sid)
  1905.       if len(self._sids[room]) == 0:
  1906.         del self._sids[room]
  1907.     except KeyError:
  1908.       pass
  1909.  
  1910.   def clearSessionIds(self, room):
  1911.     try:
  1912.       del self._sids[room]
  1913.     except KeyError:
  1914.       pass
  1915.  
  1916.   def hasSessionId(self, room, sid):
  1917.     try:
  1918.       if sid in self._sids[room]:
  1919.         return True
  1920.       else:
  1921.         return False
  1922.     except KeyError:
  1923.       return False
  1924.  
  1925.   ####
  1926.   # Repr
  1927.   ####
  1928.   def __repr__(self):
  1929.     return "<User: %s>" %(self.name)
  1930.  
  1931. ################################################################
  1932. # Message class
  1933. ################################################################
  1934. class Message:
  1935.   """Class that represents a message."""
  1936.   ####
  1937.   # Attach/detach
  1938.   ####
  1939.   def attach(self, room, msgid):
  1940.     """
  1941.    Attach the Message to a message id.
  1942.  
  1943.    @type msgid: str
  1944.    @param msgid: message id
  1945.    """
  1946.     if self._msgid == None:
  1947.       self._room = room
  1948.       self._msgid = msgid
  1949.       self._room._msgs[msgid] = self
  1950.  
  1951.   def detach(self):
  1952.     """Detach the Message."""
  1953.     if self._msgid != None and self._msgid in self._room._msgs:
  1954.       del self._room._msgs[self._msgid]
  1955.       self._msgid = None
  1956.  
  1957.   ####
  1958.   # Init
  1959.   ####
  1960.   def __init__(self, **kw):
  1961.     self._msgid = None
  1962.     self._time = None
  1963.     self._user = None
  1964.     self._body = None
  1965.     self._room = None
  1966.     self._raw = ""
  1967.     self._ip = None
  1968.     self._unid = ""
  1969.     self._nameColor = "000"
  1970.     self._fontSize = 12
  1971.     self._fontFace = "0"
  1972.     self._fontColor = "000"
  1973.     for attr, val in kw.items():
  1974.       if val == None: continue
  1975.       setattr(self, "_" + attr, val)
  1976.  
  1977.   ####
  1978.   # Properties
  1979.   ####
  1980.   def getId(self): return self._msgid
  1981.   def getTime(self): return self._time
  1982.   def getUser(self): return self._user
  1983.   def getBody(self): return self._body
  1984.   def getIP(self): return self._ip
  1985.   def getFontColor(self): return self._fontColor
  1986.   def getFontFace(self): return self._fontFace
  1987.   def getFontSize(self): return self._fontSize
  1988.   def getNameColor(self): return self._nameColor
  1989.   def getRoom(self): return self._room
  1990.   def getRaw(self): return self._raw
  1991.   def getUnid(self): return self._unid
  1992.  
  1993.   msgid = property(getId)
  1994.   time = property(getTime)
  1995.   user = property(getUser)
  1996.   body = property(getBody)
  1997.   room = property(getRoom)
  1998.   ip = property(getIP)
  1999.   fontColor = property(getFontColor)
  2000.   fontFace = property(getFontFace)
  2001.   fontSize = property(getFontSize)
  2002.   raw = property(getRaw)
  2003.   nameColor = property(getNameColor)
  2004.   unid = property(getUnid)