Advertisement
Thelorgoreng

ch

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