Advertisement
Lyend

ch.py

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