Advertisement
Guest User

Untitled

a guest
Aug 23rd, 2017
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 50.89 KB | None | 0 0
  1.  
  2.  
  3. ################################################################
  4.  
  5. # File: ch.py
  6.  
  7. # Title: Chatango Library
  8.  
  9. # Author: Lumirayz <lumirayz@gmail.com>
  10.  
  11. # Version: 1.3
  12.  
  13. # Description:
  14.  
  15. # An event-based library for connecting to one or multiple Chatango rooms, has
  16.  
  17. # support for several things including: messaging, message font,
  18.  
  19. # name color, deleting, banning, recent history, 2 userlist modes,
  20.  
  21. # flagging, avoiding flood bans, detecting flags, message bg in pm's, idling in pm's.
  22.  
  23. ################################################################
  24.  
  25.  
  26.  
  27. ################################################################
  28.  
  29. # License
  30.  
  31. ################################################################
  32.  
  33. # Copyright 2011 Lumirayz
  34.  
  35. # This program is distributed under the terms of the GNU GPL.
  36.  
  37.  
  38.  
  39. ################################################################
  40.  
  41. # Imports
  42.  
  43. ################################################################
  44.  
  45. import socket
  46.  
  47. import threading
  48.  
  49. import time
  50.  
  51. import random
  52.  
  53. import re
  54.  
  55. import sys
  56.  
  57. import select
  58.  
  59. import os
  60.  
  61. ################################################################
  62.  
  63. # Python 2 compatibility
  64.  
  65. ################################################################
  66.  
  67.  
  68. if sys.version_info[0] < 3:
  69.  
  70. class urllib:
  71.  
  72. parse = __import__("urllib")
  73.  
  74. request = __import__("urllib2")
  75.  
  76. input = raw_input
  77.  
  78. else:
  79.  
  80. import urllib.request
  81.  
  82. import urllib.parse
  83.  
  84.  
  85.  
  86. ################################################################
  87.  
  88. # Constants
  89.  
  90. ################################################################
  91.  
  92. Userlist_Recent = 0
  93.  
  94. Userlist_All = 1
  95.  
  96.  
  97.  
  98. BigMessage_Multiple = 0
  99.  
  100. BigMessage_Cut = 1
  101.  
  102.  
  103.  
  104. ################################################################
  105.  
  106. # Tagserver stuff
  107.  
  108. ################################################################
  109.  
  110. specials = {'mitvcanal': 56, 'magicc666': 67, 'livenfree': 18, 'eplsiite': 56, 'soccerjumbo2': 21, 'bguk': 67, 'animachat20': 34, 'cricket365live': 21, 'pokemonepisodeorg': 22, 'sport24lt': 56, 'mywowpinoy': 5, 'phnoytalk': 21, 'futboldirectochat': 67, 'flowhot-chat-online': 12, 'watchanimeonn': 26, 'cricvid-hitcric-': 51, 'bobproctor': 56, 'fullsportshd2': 18, 'chia-anime': 12, 'narutochatt': 52, 'animeproxer': 55, 'ttvsports': 56, 'leeplarp': 27, 'portalsports': 18, 'stream2watch3': 56, 'proudlypinoychat': 51, 'ver-anime': 34, 'iluvpinas': 53, 'vipstand': 21, 'eafangames': 56, 'worldfootballusch2': 18, 'soccerjumbo': 21, 'myfoxdfw': 67, 'animelinkz': 20, 'rgsmotrisport': 51, 'bateriafina-8': 8, 'as-chatroom': 10, 'dbzepisodeorg': 12, 'watch-dragonball': 19, 'narutowire': 10, 'tvanimefreak': 54}
  111.  
  112. 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]]
  113.  
  114.  
  115.  
  116. def getServer(group):
  117.  
  118. """
  119.  
  120. Get the server host for a certain room.
  121.  
  122.  
  123.  
  124. @type group: str
  125.  
  126. @param group: room name
  127.  
  128.  
  129.  
  130. @rtype: str
  131.  
  132. @return: the server's hostname
  133.  
  134. """
  135.  
  136. try:
  137.  
  138. sn = specials[group]
  139.  
  140. except KeyError:
  141.  
  142. group = group.replace("_", "q")
  143.  
  144. group = group.replace("-", "q")
  145.  
  146. fnv = float(int(group[0:min(5, len(group))], 36))
  147.  
  148. lnv = group[6: (6 + min(3, len(group) - 5))]
  149.  
  150. if(lnv):
  151.  
  152. lnv = float(int(lnv, 36))
  153.  
  154. if(lnv <= 1000):
  155.  
  156. lnv = 1000
  157.  
  158. else:
  159.  
  160. lnv = 1000
  161.  
  162. num = (fnv % lnv) / lnv
  163.  
  164. maxnum = sum(map(lambda x: x[1], tsweights))
  165.  
  166. cumfreq = 0
  167.  
  168. sn = 0
  169.  
  170. for wgt in tsweights:
  171.  
  172. cumfreq += float(wgt[1]) / maxnum
  173.  
  174. if(num <= cumfreq):
  175.  
  176. sn = int(wgt[0])
  177.  
  178. break
  179.  
  180. return "s" + str(sn) + ".chatango.com"
  181.  
  182.  
  183.  
  184. ################################################################
  185.  
  186. # Uid
  187.  
  188. ################################################################
  189.  
  190. def genUid():
  191.  
  192. return str(random.randrange(10 ** 15, 10 ** 16))
  193.  
  194.  
  195.  
  196. ################################################################
  197.  
  198. # Message stuff
  199.  
  200. ################################################################
  201.  
  202. def clean_message(msg):
  203.  
  204. """
  205.  
  206. Clean a message and return the message, n tag and f tag.
  207.  
  208.  
  209.  
  210. @type msg: str
  211.  
  212. @param msg: the message
  213.  
  214.  
  215.  
  216. @rtype: str, str, str
  217.  
  218. @returns: cleaned message, n tag contents, f tag contents
  219.  
  220. """
  221.  
  222. n = re.search("<n(.*?)/>", msg)
  223.  
  224. if n: n = n.group(1)
  225.  
  226. f = re.search("<f(.*?)>", msg)
  227.  
  228. if f: f = f.group(1)
  229.  
  230. msg = re.sub("<n.*?/>", "", msg)
  231.  
  232. msg = re.sub("<f.*?>", "", msg)
  233.  
  234. msg = strip_html(msg)
  235.  
  236. msg = msg.replace("&lt;", "<")
  237.  
  238. msg = msg.replace("&gt;", ">")
  239.  
  240. msg = msg.replace("&quot;", "\"")
  241.  
  242. msg = msg.replace("&apos;", "'")
  243.  
  244. msg = msg.replace("&amp;", "&")
  245.  
  246. return msg, n, f
  247.  
  248.  
  249.  
  250. def strip_html(msg):
  251.  
  252. """Strip HTML."""
  253.  
  254. li = msg.split("<")
  255.  
  256. if len(li) == 1:
  257.  
  258. return li[0]
  259.  
  260. else:
  261.  
  262. ret = list()
  263.  
  264. for data in li:
  265.  
  266. data = data.split(">", 1)
  267.  
  268. if len(data) == 1:
  269.  
  270. ret.append(data[0])
  271.  
  272. elif len(data) == 2:
  273.  
  274. ret.append(data[1])
  275.  
  276. return "".join(ret)
  277.  
  278.  
  279.  
  280. def parseNameColor(n):
  281.  
  282. """This just returns its argument, should return the name color."""
  283.  
  284. #probably is already the name
  285.  
  286. return n
  287.  
  288.  
  289.  
  290. def parseFont(f):
  291.  
  292. """Parses the contents of a f tag and returns color, face and size."""
  293.  
  294. #' xSZCOL="FONT"'
  295.  
  296. try: #TODO: remove quick hack
  297.  
  298. sizecolor, fontface = f.split("=", 1)
  299.  
  300. sizecolor = sizecolor.strip()
  301.  
  302. size = int(sizecolor[1:3])
  303.  
  304. col = sizecolor[3:6]
  305.  
  306. if col == "": col = None
  307.  
  308. face = f.split("\"", 2)[1]
  309.  
  310. return col, face, size
  311.  
  312. except:
  313.  
  314. return None, None, None
  315.  
  316.  
  317.  
  318. ################################################################
  319.  
  320. # Anon id
  321.  
  322. ################################################################
  323.  
  324.  
  325.  
  326. # i don't know what the fuck got into my head while i wrote this, so
  327.  
  328. # TODO: FIX DIS SHIT
  329.  
  330. def getAnonId(n, ssid):
  331.  
  332. """Gets the anon's id."""
  333.  
  334. if n == None: n = "5504"
  335.  
  336. try:
  337.  
  338. return "".join(list(
  339.  
  340. map(lambda x: str(x[0] + x[1])[-1], list(zip(
  341.  
  342. list(map(lambda x: int(x), n)),
  343.  
  344. list(map(lambda x: int(x), ssid[4:]))
  345.  
  346. )))
  347.  
  348. ))
  349.  
  350. except ValueError:
  351.  
  352. return "NNNN"
  353.  
  354.  
  355.  
  356. ################################################################
  357.  
  358. # PM Auth
  359.  
  360. ################################################################
  361.  
  362. auth_re = re.compile(r"auth\.chatango\.com ?= ?([^;]*)", re.IGNORECASE)
  363.  
  364.  
  365.  
  366. def _getAuth(name, password):
  367.  
  368. """
  369.  
  370. Request an auid using name and password.
  371.  
  372.  
  373.  
  374. @type name: str
  375.  
  376. @param name: name
  377.  
  378. @type password: str
  379.  
  380. @param password: password
  381.  
  382.  
  383.  
  384. @rtype: str
  385.  
  386. @return: auid
  387.  
  388. """
  389.  
  390. data = urllib.parse.urlencode({
  391.  
  392. "user_id": name,
  393.  
  394. "password": password,
  395.  
  396. "storecookie": "on",
  397.  
  398. "checkerrors": "yes"
  399.  
  400. }).encode()
  401.  
  402. try:
  403.  
  404. resp = urllib.request.urlopen("http://chatango.com/login", data)
  405.  
  406. headers = resp.headers
  407.  
  408. except Exception:
  409.  
  410. return None
  411.  
  412. for header, value in headers.items():
  413.  
  414. if header.lower() == "set-cookie":
  415.  
  416. m = auth_re.search(value)
  417.  
  418. if m:
  419.  
  420. auth = m.group(1)
  421.  
  422. if auth == "":
  423.  
  424. return None
  425.  
  426. return auth
  427.  
  428. return None
  429.  
  430.  
  431.  
  432. ################################################################
  433.  
  434. # PM class
  435.  
  436. ################################################################
  437.  
  438. class PM(object):
  439.  
  440. """Manages a connection with Chatango PM."""
  441.  
  442. ####
  443.  
  444. # Init
  445.  
  446. ####
  447.  
  448. def __init__(self, mgr):
  449.  
  450. self._connected = False
  451.  
  452. self._mgr = mgr
  453.  
  454. self._idle = False
  455.  
  456. self.idle = 1
  457.  
  458. self._auid = None
  459.  
  460. self._blocklist = set()
  461.  
  462. self._contacts = set()
  463.  
  464. self._wlock = False
  465.  
  466. self._premium = False
  467.  
  468. self._firstCommand = True
  469.  
  470. self._wbuf = b""
  471.  
  472. self._wlockbuf = b""
  473.  
  474. self._rbuf = b""
  475.  
  476. self._pingTask = None
  477.  
  478. self._connect()
  479.  
  480.  
  481.  
  482. ####
  483.  
  484. # Connections
  485.  
  486. ####
  487.  
  488. def _connect(self, idle = False):
  489.  
  490. self._wbuf = b""
  491.  
  492. self._sock = socket.socket()
  493.  
  494. self._sock.connect((self._mgr._PMHost, self._mgr._PMPort))
  495.  
  496. self._sock.setblocking(False)
  497.  
  498. self._firstCommand = True
  499.  
  500. if not self._auth(): return
  501.  
  502. self._pingTask = self.mgr.setInterval(self._mgr._pingDelay, self.ping)
  503.  
  504. self._connected = True
  505.  
  506. if idle: self._idle = True
  507.  
  508.  
  509.  
  510. def _auth(self):
  511.  
  512. self._auid = _getAuth(self._mgr.name, self._mgr.password)
  513.  
  514. if self._auid == None:
  515.  
  516. self._sock.close()
  517.  
  518. self._callEvent("onLoginFail")
  519.  
  520. self._sock = None
  521.  
  522. return False
  523.  
  524. self._sendCommand("tlogin", self._auid, "2")
  525.  
  526. self._setWriteLock(True)
  527.  
  528. return True
  529.  
  530.  
  531.  
  532. def disconnect(self):
  533.  
  534. self._disconnect()
  535.  
  536. self._callEvent("onPMDisconnect")
  537.  
  538.  
  539.  
  540. def _disconnect(self):
  541.  
  542. self._connected = False
  543.  
  544. self._sock.close()
  545.  
  546. self._sock = None
  547.  
  548.  
  549.  
  550. ####
  551.  
  552. # Feed
  553.  
  554. ####
  555.  
  556. def _feed(self, data):
  557.  
  558. """
  559.  
  560. Feed data to the connection.
  561.  
  562.  
  563.  
  564. @type data: bytes
  565.  
  566. @param data: data to be fed
  567.  
  568. """
  569.  
  570. self._rbuf += data
  571.  
  572. while self._rbuf.find(b"\x00") != -1:
  573.  
  574. data = self._rbuf.split(b"\x00")
  575.  
  576. for food in data[:-1]:
  577.  
  578. self._process(food.decode("latin-1").rstrip("\r\n")) #numnumz ;3
  579.  
  580. self._rbuf = data[-1]
  581.  
  582.  
  583.  
  584. def _process(self, data):
  585.  
  586. """
  587.  
  588. Process a command string.
  589.  
  590.  
  591.  
  592. @type data: str
  593.  
  594. @param data: the command string
  595.  
  596. """
  597.  
  598. self._callEvent("onRaw", data)
  599.  
  600. data = data.split(":")
  601.  
  602. cmd, args = data[0], data[1:]
  603.  
  604. func = "rcmd_" + cmd
  605.  
  606. if hasattr(self, func):
  607.  
  608. getattr(self, func)(args)
  609.  
  610.  
  611.  
  612. ####
  613.  
  614. # Properties
  615.  
  616. ####
  617.  
  618. def getManager(self): return self._mgr
  619.  
  620. def getContacts(self): return self._contacts
  621.  
  622. def getBlocklist(self): return self._blocklist
  623.  
  624.  
  625.  
  626. mgr = property(getManager)
  627.  
  628. contacts = property(getContacts)
  629.  
  630. blocklist = property(getBlocklist)
  631.  
  632.  
  633.  
  634. ####
  635.  
  636. # Received Commands
  637.  
  638. ####
  639.  
  640. def rcmd_OK(self, args):
  641.  
  642. self._setWriteLock(False)
  643.  
  644. self._sendCommand("wl")
  645.  
  646. self._sendCommand("getblock")
  647.  
  648. self._sendCommand("getpremium", "1")
  649.  
  650. if self._idle:
  651.  
  652. self.setIdle()
  653.  
  654. self._callEvent("onPMConnect")
  655.  
  656.  
  657.  
  658. def rcmd_wl(self, args):
  659.  
  660. self._contacts = set()
  661.  
  662. for i in range(len(args) // 4):
  663.  
  664. name, last_on, is_on, idle = args[i * 4: i * 4 + 4]
  665.  
  666. user = User(name)
  667.  
  668. self._contacts.add(user)
  669.  
  670. self._callEvent("onPMContactlistReceive")
  671.  
  672.  
  673.  
  674. def rcmd_block_list(self, args):
  675.  
  676. self._blocklist = set()
  677.  
  678. for name in args:
  679.  
  680. if name == "": continue
  681.  
  682. self._blocklist.add(User(name))
  683.  
  684.  
  685.  
  686. def rcmd_DENIED(self, args):
  687.  
  688. self._disconnect()
  689.  
  690. self._callEvent("onLoginFail")
  691.  
  692.  
  693.  
  694. def rcmd_msg(self, args):
  695.  
  696. user = User(args[0])
  697.  
  698. body = strip_html(":".join(args[5:]))
  699.  
  700. self._callEvent("onPMMessage", user, body)
  701.  
  702.  
  703.  
  704. def rcmd_msgoff(self, args):
  705.  
  706. user = User(args[0])
  707.  
  708. body = strip_html(":".join(args[5:]))
  709.  
  710. self._callEvent("onPMOfflineMessage", user, body)
  711.  
  712.  
  713.  
  714. def rcmd_wlonline(self, args):
  715.  
  716. self._callEvent("onPMContactOnline", User(args[0]))
  717.  
  718.  
  719.  
  720. def rcmd_wloffline(self, args):
  721.  
  722. self._callEvent("onPMContactOffline", User(args[0]))
  723.  
  724.  
  725.  
  726. def rcmd_premium(self, args):
  727.  
  728. if float(args[1]) > time.time():
  729.  
  730. self._premium = True
  731.  
  732. if self._mgr.user._mbg: self.setBgMode(1)
  733.  
  734. if self._mgr.user._mrec: self.setRecordingMode(1)
  735.  
  736. else:
  737.  
  738. self._premium = False
  739.  
  740.  
  741.  
  742. def rcmd_kickingoff(self, args):
  743.  
  744. self.disconnect()
  745.  
  746.  
  747.  
  748. ####
  749.  
  750. # Commands
  751.  
  752. ####
  753.  
  754. def ping(self):
  755.  
  756. self._sendCommand("")
  757.  
  758. self._callEvent("onPMPing")
  759.  
  760. if self._idle:
  761.  
  762. if self.idle != 0:
  763.  
  764. self.setIdle()
  765.  
  766. self.idle = 0
  767.  
  768.  
  769.  
  770. def message(self, user, msg):
  771.  
  772. self.setActive()
  773.  
  774. if self._idle:
  775.  
  776. self.idle = 1
  777.  
  778. msg = msg.replace("<b>", "<B>").replace("<u>", "<U>").replace("<i>", "<I>").replace("</b>", "</B>").replace("</u>", "</U>").replace("</i>", "</I>")
  779.  
  780. self._sendCommand("msg", user.name, "<n%s/><m v=\"1\"><g xs0=\"0\"><g x%ss%s=\"6\">%s</g></g></m>" % (self._mgr.user.nameColor.lower(), self._mgr.user.fontSize, self._mgr.user.fontColor.lower(), msg))
  781.  
  782.  
  783.  
  784. def test(self, user):
  785.  
  786. self._sendCommand("connect", user.name)
  787.  
  788.  
  789.  
  790. def addContact(self, user):
  791.  
  792. if user not in self._contacts:
  793.  
  794. self._sendCommand("wladd", user.name)
  795.  
  796. self._contacts.add(user)
  797.  
  798. self._callEvent("onPMContactAdd", user)
  799.  
  800.  
  801.  
  802. def removeContact(self, user):
  803.  
  804. if user in self._contacts:
  805.  
  806. self._sendCommand("wldelete", user.name)
  807.  
  808. self._contacts.remove(user)
  809.  
  810. self._callEvent("onPMContactRemove", user)
  811.  
  812.  
  813.  
  814. def block(self, user):
  815.  
  816. if user not in self._blocklist:
  817.  
  818. self._sendCommand("block", user.name, user.name, "S")
  819.  
  820. self._block.add(user)
  821.  
  822. self._callEvent("onPMBlock", user)
  823.  
  824.  
  825.  
  826. def unblock(self, user):
  827.  
  828. if user in self._blocklist:
  829.  
  830. self._sendCommand("unblock", user.name)
  831.  
  832. self._block.remove(user)
  833.  
  834. self._callEvent("onPMUnblock", user)
  835.  
  836.  
  837.  
  838. def setBgMode(self, mode):
  839.  
  840. self._sendCommand("msgbg", str(mode))
  841.  
  842.  
  843.  
  844. def setRecordingMode(self, mode):
  845.  
  846. self._sendCommand("msgmedia", str(mode))
  847.  
  848.  
  849.  
  850. def setIdle(self):
  851.  
  852. self._sendCommand("idle:0")
  853.  
  854.  
  855.  
  856. def setActive(self):
  857.  
  858. self._sendCommand("idle:1")
  859.  
  860. ####
  861.  
  862. # Util
  863.  
  864. ####
  865.  
  866. def _callEvent(self, evt, *args, **kw):
  867.  
  868. getattr(self.mgr, evt)(self, *args, **kw)
  869.  
  870. self.mgr.onEventCalled(self, evt, *args, **kw)
  871.  
  872.  
  873.  
  874. def _write(self, data):
  875.  
  876. if self._wlock:
  877.  
  878. self._wlockbuf += data
  879.  
  880. else:
  881.  
  882. self.mgr._write(self, data)
  883.  
  884.  
  885.  
  886. def _setWriteLock(self, lock):
  887.  
  888. self._wlock = lock
  889.  
  890. if self._wlock == False:
  891.  
  892. self._write(self._wlockbuf)
  893.  
  894. self._wlockbuf = b""
  895.  
  896.  
  897.  
  898. def _sendCommand(self, *args):
  899.  
  900. """
  901.  
  902. Send a command.
  903.  
  904.  
  905.  
  906. @type args: [str, str, ...]
  907.  
  908. @param args: command and list of arguments
  909.  
  910. """
  911.  
  912. if self._firstCommand:
  913.  
  914. terminator = b"\x00"
  915.  
  916. self._firstCommand = False
  917.  
  918. else:
  919.  
  920. terminator = b"\r\n\x00"
  921.  
  922. self._write(":".join(args).encode() + terminator)
  923.  
  924.  
  925.  
  926. ################################################################
  927.  
  928. # Room class
  929.  
  930. ################################################################
  931.  
  932. class Room(object):
  933.  
  934. """Manages a connection with a Chatango room."""
  935.  
  936. ####
  937.  
  938. # Init
  939.  
  940. ####
  941.  
  942. def __init__(self, room, uid = None, server = None, port = None, mgr = None):
  943.  
  944. # Basic stuff
  945.  
  946. self._name = room
  947.  
  948. self._server = server or getServer(room)
  949.  
  950. self._port = port or 443
  951.  
  952. self._mgr = mgr
  953.  
  954.  
  955.  
  956. # Under the hood
  957.  
  958. self._connected = False
  959.  
  960. self._reconnecting = False
  961.  
  962. self._uid = uid or genUid()
  963.  
  964. self._rbuf = b""
  965.  
  966. self._wbuf = b""
  967.  
  968. self._wlockbuf = b""
  969.  
  970. self._owner = None
  971.  
  972. self._mods = set()
  973.  
  974. self._mqueue = dict()
  975.  
  976. self._history = list()
  977.  
  978. self._userlist = list()
  979.  
  980. self._firstCommand = True
  981.  
  982. self._connectAmmount = 0
  983.  
  984. self._premium = False
  985.  
  986. self._userCount = 0
  987.  
  988. self._pingTask = None
  989.  
  990. self._users = dict()
  991.  
  992. self._msgs = dict()
  993.  
  994. self._wlock = False
  995.  
  996. self._silent = False
  997.  
  998. self._banlist = list()
  999.  
  1000.  
  1001.  
  1002. # Inited vars
  1003.  
  1004. if self._mgr: self._connect()
  1005.  
  1006.  
  1007.  
  1008. ####
  1009.  
  1010. # User and Message management
  1011.  
  1012. ####
  1013.  
  1014. def getMessage(self, mid):
  1015.  
  1016. return self._msgs.get(mid)
  1017.  
  1018.  
  1019.  
  1020. def createMessage(self, msgid, **kw):
  1021.  
  1022. if msgid not in self._msgs:
  1023.  
  1024. msg = Message(msgid = msgid, **kw)
  1025.  
  1026. self._msgs[msgid] = msg
  1027.  
  1028. else:
  1029.  
  1030. msg = self._msgs[msgid]
  1031.  
  1032. return msg
  1033.  
  1034.  
  1035.  
  1036. ####
  1037.  
  1038. # Connect/disconnect
  1039.  
  1040. ####
  1041.  
  1042. def _connect(self):
  1043.  
  1044. """Connect to the server."""
  1045.  
  1046. self._sock = socket.socket()
  1047.  
  1048. self._sock.connect((self._server, self._port))
  1049.  
  1050. self._sock.setblocking(False)
  1051.  
  1052. self._firstCommand = True
  1053.  
  1054. self._wbuf = b""
  1055.  
  1056. self._auth()
  1057.  
  1058. self._pingTask = self.mgr.setInterval(self.mgr._pingDelay, self.ping)
  1059.  
  1060. if not self._reconnecting: self.connected = True
  1061.  
  1062.  
  1063.  
  1064. def reconnect(self):
  1065.  
  1066. """Reconnect."""
  1067.  
  1068. self._reconnect()
  1069.  
  1070.  
  1071.  
  1072. def _reconnect(self):
  1073.  
  1074. """Reconnect."""
  1075.  
  1076. self._reconnecting = True
  1077.  
  1078. if self.connected:
  1079.  
  1080. self._disconnect()
  1081.  
  1082. self._uid = genUid()
  1083.  
  1084. self._connect()
  1085.  
  1086. self._reconnecting = False
  1087.  
  1088.  
  1089.  
  1090. def disconnect(self):
  1091.  
  1092. """Disconnect."""
  1093.  
  1094. self._disconnect()
  1095.  
  1096. self._callEvent("onDisconnect")
  1097.  
  1098.  
  1099.  
  1100. def _disconnect(self):
  1101.  
  1102. """Disconnect from the server."""
  1103.  
  1104. if not self._reconnecting: self.connected = False
  1105.  
  1106. for user in self._userlist:
  1107.  
  1108. user.clearSessionIds(self)
  1109.  
  1110. self._userlist = list()
  1111.  
  1112. self._pingTask.cancel()
  1113.  
  1114. self._sock.close()
  1115.  
  1116. if not self._reconnecting: del self.mgr._rooms[self.name]
  1117.  
  1118.  
  1119.  
  1120. def _auth(self):
  1121.  
  1122. """Authenticate."""
  1123.  
  1124. self._sendCommand("bauth", self.name, self._uid, self.mgr.name, self.mgr.password)
  1125.  
  1126. self._setWriteLock(True)
  1127.  
  1128.  
  1129.  
  1130. ####
  1131.  
  1132. # Properties
  1133.  
  1134. ####
  1135.  
  1136. def getName(self): return self._name
  1137.  
  1138. def getManager(self): return self._mgr
  1139.  
  1140. def getUserlist(self, mode = None, unique = None, memory = None):
  1141.  
  1142. ul = None
  1143.  
  1144. if mode == None: mode = self.mgr._userlistMode
  1145.  
  1146. if unique == None: unique = self.mgr._userlistUnique
  1147.  
  1148. if memory == None: memory = self.mgr._userlistMemory
  1149.  
  1150. if mode == Userlist_Recent:
  1151.  
  1152. ul = map(lambda x: x.user, self._history[-memory:])
  1153.  
  1154. elif mode == Userlist_All:
  1155.  
  1156. ul = self._userlist
  1157.  
  1158. if unique:
  1159.  
  1160. return list(set(ul))
  1161.  
  1162. else:
  1163.  
  1164. return ul
  1165.  
  1166. def getUserNames(self):
  1167.  
  1168. ul = self.userlist
  1169.  
  1170. return list(map(lambda x: x.name, ul))
  1171.  
  1172. def getUser(self): return self.mgr.user
  1173.  
  1174. def getOwner(self): return self._owner
  1175.  
  1176. def getOwnerName(self): return self._owner.name
  1177.  
  1178. def getMods(self):
  1179.  
  1180. newset = set()
  1181.  
  1182. for mod in self._mods:
  1183.  
  1184. newset.add(mod)
  1185.  
  1186. return newset
  1187.  
  1188. def getModNames(self):
  1189.  
  1190. mods = self.getMods()
  1191.  
  1192. return [x.name for x in mods]
  1193.  
  1194. def getUserCount(self): return self._userCount
  1195.  
  1196. def getSilent(self): return self._silent
  1197.  
  1198. def setSilent(self, val): self._silent = val
  1199.  
  1200. def getBanlist(self): return [record[2] for record in self._banlist]
  1201.  
  1202.  
  1203.  
  1204. name = property(getName)
  1205.  
  1206. mgr = property(getManager)
  1207.  
  1208. userlist = property(getUserlist)
  1209.  
  1210. usernames = property(getUserNames)
  1211.  
  1212. user = property(getUser)
  1213.  
  1214. owner = property(getOwner)
  1215.  
  1216. ownername = property(getOwnerName)
  1217.  
  1218. mods = property(getMods)
  1219.  
  1220. modnames = property(getModNames)
  1221.  
  1222. usercount = property(getUserCount)
  1223.  
  1224. silent = property(getSilent, setSilent)
  1225.  
  1226. banlist = property(getBanlist)
  1227.  
  1228.  
  1229.  
  1230. ####
  1231.  
  1232. # Feed/process
  1233.  
  1234. ####
  1235.  
  1236. def _feed(self, data):
  1237.  
  1238. """
  1239.  
  1240. Feed data to the connection.
  1241.  
  1242.  
  1243.  
  1244. @type data: bytes
  1245.  
  1246. @param data: data to be fed
  1247.  
  1248. """
  1249.  
  1250. self._rbuf += data
  1251.  
  1252. while self._rbuf.find(b"\x00") != -1:
  1253.  
  1254. data = self._rbuf.split(b"\x00")
  1255.  
  1256. for food in data[:-1]:
  1257. try:
  1258. self._process(food.decode("latin-1").rstrip("\r\n")) #numnumz ;3
  1259. except:
  1260. pass
  1261. self._rbuf = data[-1]
  1262.  
  1263.  
  1264.  
  1265. def _process(self, data):
  1266.  
  1267. """
  1268.  
  1269. Process a command string.
  1270.  
  1271.  
  1272.  
  1273. @type data: str
  1274.  
  1275. @param data: the command string
  1276.  
  1277. """
  1278.  
  1279. self._callEvent("onRaw", data)
  1280.  
  1281. data = data.split(":")
  1282.  
  1283. cmd, args = data[0], data[1:]
  1284.  
  1285. func = "rcmd_" + cmd
  1286.  
  1287. if hasattr(self, func):
  1288.  
  1289. getattr(self, func)(args)
  1290.  
  1291.  
  1292.  
  1293. ####
  1294.  
  1295. # Received Commands
  1296.  
  1297. ####
  1298.  
  1299. def rcmd_ok(self, args):
  1300.  
  1301. if args[2] != "M": #unsuccesful login
  1302.  
  1303. self._callEvent("onLoginFail")
  1304.  
  1305. self.disconnect()
  1306.  
  1307. self._owner = User(args[0])
  1308.  
  1309. self._uid = args[1]
  1310.  
  1311. self._aid = args[1][4:8]
  1312.  
  1313. self._mods = set(map(lambda x: User(x), args[6].split(";")))
  1314.  
  1315. self._i_log = list()
  1316.  
  1317.  
  1318.  
  1319. def rcmd_denied(self, args):
  1320.  
  1321. self._disconnect()
  1322.  
  1323. self._callEvent("onConnectFail")
  1324.  
  1325.  
  1326.  
  1327. def rcmd_inited(self, args):
  1328.  
  1329. self._sendCommand("g_participants", "start")
  1330.  
  1331. self._sendCommand("getpremium", "1")
  1332.  
  1333. self.requestBanlist()
  1334.  
  1335. if self._connectAmmount == 0:
  1336.  
  1337. self._callEvent("onConnect")
  1338.  
  1339. for msg in reversed(self._i_log):
  1340.  
  1341. user = msg.user
  1342.  
  1343. self._callEvent("onHistoryMessage", user, msg)
  1344.  
  1345. self._addHistory(msg)
  1346.  
  1347. del self._i_log
  1348.  
  1349. else:
  1350.  
  1351. self._callEvent("onReconnect")
  1352.  
  1353. self._connectAmmount += 1
  1354.  
  1355. self._setWriteLock(False)
  1356.  
  1357.  
  1358.  
  1359. def rcmd_premium(self, args):
  1360.  
  1361. if float(args[1]) > time.time():
  1362.  
  1363. self._premium = True
  1364.  
  1365. if self.user._mbg: self.setBgMode(1)
  1366.  
  1367. if self.user._mrec: self.setRecordingMode(1)
  1368.  
  1369. else:
  1370.  
  1371. self._premium = False
  1372.  
  1373.  
  1374.  
  1375. def rcmd_mods(self, args):
  1376.  
  1377. modnames = args
  1378.  
  1379. mods = set(map(lambda x: User(x), modnames))
  1380.  
  1381. premods = self._mods
  1382.  
  1383. for user in mods - premods: #modded
  1384.  
  1385. self._mods.add(user)
  1386.  
  1387. self._callEvent("onModAdd", user)
  1388.  
  1389. for user in premods - mods: #demodded
  1390.  
  1391. self._mods.remove(user)
  1392.  
  1393. self._callEvent("onModRemove", user)
  1394.  
  1395. self._callEvent("onModChange")
  1396.  
  1397.  
  1398.  
  1399. def rcmd_b(self, args):
  1400.  
  1401. mtime = float(args[0])
  1402.  
  1403. puid = args[3]
  1404.  
  1405. ip = args[6]
  1406.  
  1407. name = args[1]
  1408.  
  1409. rawmsg = ":".join(args[9:])
  1410.  
  1411. msg, n, f = clean_message(rawmsg)
  1412.  
  1413. if name == "":
  1414.  
  1415. nameColor = None
  1416.  
  1417. name = "#" + args[2]
  1418.  
  1419. if name == "#":
  1420.  
  1421. name = "!anon" + getAnonId(n, puid)
  1422.  
  1423. else:
  1424.  
  1425. if n: nameColor = parseNameColor(n)
  1426.  
  1427. else: nameColor = None
  1428.  
  1429. i = args[5]
  1430.  
  1431. unid = args[4]
  1432.  
  1433. #Create an anonymous message and queue it because msgid is unknown.
  1434.  
  1435. if f: fontColor, fontFace, fontSize = parseFont(f)
  1436.  
  1437. else: fontColor, fontFace, fontSize = None, None, None
  1438.  
  1439. msg = Message(
  1440.  
  1441. time = mtime,
  1442.  
  1443. user = User(name),
  1444.  
  1445. body = msg,
  1446.  
  1447. raw = rawmsg,
  1448.  
  1449. uid = puid,
  1450.  
  1451. ip = ip,
  1452.  
  1453. nameColor = nameColor,
  1454.  
  1455. fontColor = fontColor,
  1456.  
  1457. fontFace = fontFace,
  1458.  
  1459. fontSize = fontSize,
  1460.  
  1461. unid = unid,
  1462.  
  1463. room = self
  1464.  
  1465. )
  1466.  
  1467. self._mqueue[i] = msg
  1468.  
  1469.  
  1470.  
  1471. def rcmd_u(self, args):
  1472.  
  1473. msg = self._mqueue[args[0]]
  1474.  
  1475. if msg.user != self.user:
  1476.  
  1477. msg.user._fontColor = msg.fontColor
  1478.  
  1479. msg.user._fontFace = msg.fontFace
  1480.  
  1481. msg.user._fontSize = msg.fontSize
  1482.  
  1483. msg.user._nameColor = msg.nameColor
  1484.  
  1485. del self._mqueue[args[0]]
  1486.  
  1487. msg.attach(self, args[1])
  1488.  
  1489. self._addHistory(msg)
  1490.  
  1491. self._callEvent("onMessage", msg.user, msg)
  1492.  
  1493.  
  1494.  
  1495. def rcmd_i(self, args):
  1496.  
  1497. mtime = float(args[0])
  1498.  
  1499. puid = args[3]
  1500.  
  1501. ip = args[6]
  1502.  
  1503. if ip == "": ip = None
  1504.  
  1505. name = args[1]
  1506.  
  1507. rawmsg = ":".join(args[8:])
  1508.  
  1509. msg, n, f = clean_message(rawmsg)
  1510.  
  1511. msgid = args[5]
  1512.  
  1513. if name == "":
  1514.  
  1515. nameColor = None
  1516.  
  1517. name = "#" + args[2]
  1518.  
  1519. if name == "#":
  1520.  
  1521. name = "!anon" + getAnonId(n, puid)
  1522.  
  1523. else:
  1524.  
  1525. if n: nameColor = parseNameColor(n)
  1526.  
  1527. else: nameColor = None
  1528.  
  1529. if f: fontColor, fontFace, fontSize = parseFont(f)
  1530.  
  1531. else: fontColor, fontFace, fontSize = None, None, None
  1532.  
  1533. msg = self.createMessage(
  1534.  
  1535. msgid = msgid,
  1536.  
  1537. time = mtime,
  1538.  
  1539. user = User(name),
  1540.  
  1541. body = msg,
  1542.  
  1543. raw = rawmsg,
  1544.  
  1545. ip = args[6],
  1546.  
  1547. unid = args[4],
  1548.  
  1549. nameColor = nameColor,
  1550.  
  1551. fontColor = fontColor,
  1552.  
  1553. fontFace = fontFace,
  1554.  
  1555. fontSize = fontSize,
  1556.  
  1557. room = self
  1558.  
  1559. )
  1560.  
  1561. if msg.user != self.user:
  1562.  
  1563. msg.user._fontColor = msg.fontColor
  1564.  
  1565. msg.user._fontFace = msg.fontFace
  1566.  
  1567. msg.user._fontSize = msg.fontSize
  1568.  
  1569. msg.user._nameColor = msg.nameColor
  1570.  
  1571. self._i_log.append(msg)
  1572.  
  1573.  
  1574.  
  1575. def rcmd_g_participants(self, args):
  1576.  
  1577. args = ":".join(args)
  1578.  
  1579. args = args.split(";")
  1580.  
  1581. for data in args:
  1582.  
  1583. data = data.split(":")
  1584.  
  1585. name = data[3].lower()
  1586.  
  1587. if name == "none": continue
  1588.  
  1589. user = User(
  1590.  
  1591. name = name,
  1592.  
  1593. room = self
  1594.  
  1595. )
  1596.  
  1597. user.addSessionId(self, data[0])
  1598.  
  1599. self._userlist.append(user)
  1600.  
  1601.  
  1602.  
  1603. def rcmd_participant(self, args):
  1604.  
  1605. if args[0] == "0": #leave
  1606.  
  1607. name = args[3].lower()
  1608.  
  1609. if name == "none": return
  1610.  
  1611. user = User(name)
  1612.  
  1613. user.removeSessionId(self, args[1])
  1614.  
  1615. self._userlist.remove(user)
  1616.  
  1617. if user not in self._userlist or not self.mgr._userlistEventUnique:
  1618.  
  1619. self._callEvent("onLeave", user)
  1620.  
  1621. else: #join
  1622.  
  1623. name = args[3].lower()
  1624.  
  1625. if name == "none": return
  1626.  
  1627. user = User(
  1628.  
  1629. name = name,
  1630.  
  1631. room = self
  1632.  
  1633. )
  1634.  
  1635. user.addSessionId(self, args[1])
  1636.  
  1637. if user not in self._userlist: doEvent = True
  1638.  
  1639. else: doEvent = False
  1640.  
  1641. self._userlist.append(user)
  1642.  
  1643. if doEvent or not self.mgr._userlistEventUnique:
  1644.  
  1645. self._callEvent("onJoin", user)
  1646.  
  1647.  
  1648.  
  1649. def rcmd_show_fw(self, args):
  1650.  
  1651. self._callEvent("onFloodWarning")
  1652.  
  1653.  
  1654.  
  1655. def rcmd_show_tb(self, args):
  1656.  
  1657. self._callEvent("onFloodBan")
  1658.  
  1659.  
  1660.  
  1661. def rcmd_tb(self, args):
  1662.  
  1663. self._callEvent("onFloodBanRepeat")
  1664.  
  1665.  
  1666.  
  1667. def rcmd_delete(self, args):
  1668.  
  1669. msg = self.getMessage(args[0])
  1670.  
  1671. if msg:
  1672.  
  1673. if msg in self._history:
  1674.  
  1675. self._history.remove(msg)
  1676.  
  1677. self._callEvent("onMessageDelete", msg.user, msg)
  1678.  
  1679. msg.detach()
  1680.  
  1681.  
  1682.  
  1683. def rcmd_deleteall(self, args):
  1684.  
  1685. for msgid in args:
  1686.  
  1687. self.rcmd_delete([msgid])
  1688.  
  1689.  
  1690.  
  1691. def rcmd_n(self, args):
  1692.  
  1693. self._userCount = int(args[0], 16)
  1694.  
  1695. self._callEvent("onUserCountChange")
  1696.  
  1697.  
  1698. def rcmd_blocklist(self, args):
  1699.  
  1700. self._banlist = list()
  1701.  
  1702. sections = ":".join(args).split(";")
  1703.  
  1704. for section in sections:
  1705.  
  1706. params = section.split(":")
  1707.  
  1708. if len(params) != 5: continue
  1709.  
  1710. if params[2] == "": continue
  1711.  
  1712. self._banlist.append((
  1713.  
  1714. params[0], #unid
  1715.  
  1716. params[1], #ip
  1717.  
  1718. User(params[2]), #target
  1719.  
  1720. float(params[3]), #time
  1721.  
  1722. User(params[4]) #src
  1723.  
  1724. ))
  1725.  
  1726. self._callEvent("onBanlistUpdate")
  1727.  
  1728.  
  1729.  
  1730. def rcmd_blocked(self, args):
  1731.  
  1732. if args[2] == "": return
  1733.  
  1734. target = User(args[2])
  1735.  
  1736. user = User(args[3])
  1737.  
  1738. self._banlist.append((args[0], args[1], target, float(args[4]), user))
  1739.  
  1740. self._callEvent("onBan", user, target)
  1741.  
  1742. self.requestBanlist()
  1743.  
  1744.  
  1745.  
  1746. def rcmd_unblocked(self, args):
  1747.  
  1748. if args[2] == "": return
  1749.  
  1750. target = User(args[2])
  1751.  
  1752. user = User(args[3])
  1753.  
  1754. self._callEvent("onUnban", user, target)
  1755.  
  1756. self.requestBanlist()
  1757.  
  1758.  
  1759.  
  1760. ####
  1761.  
  1762. # Commands
  1763.  
  1764. ####
  1765.  
  1766. def ping(self):
  1767.  
  1768. """Send a ping."""
  1769.  
  1770. self._sendCommand("")
  1771.  
  1772. self._callEvent("onPing")
  1773.  
  1774.  
  1775.  
  1776. def rawMessage(self, msg):
  1777.  
  1778. """
  1779.  
  1780. Send a message without n and f tags.
  1781.  
  1782.  
  1783.  
  1784. @type msg: str
  1785.  
  1786. @param msg: message
  1787.  
  1788. """
  1789.  
  1790. if not self._silent:
  1791.  
  1792. self._sendCommand("bmsg:tl2r", msg)
  1793.  
  1794.  
  1795.  
  1796. def message(self, msg, html = False):
  1797.  
  1798. """
  1799.  
  1800. Send a message.
  1801.  
  1802.  
  1803.  
  1804. @type msg: str
  1805.  
  1806. @param msg: message
  1807.  
  1808. """
  1809.  
  1810. if not html:
  1811.  
  1812. msg = msg.replace("<", "&lt;").replace(">", "&gt;")
  1813.  
  1814. if len(msg) > self.mgr._maxLength:
  1815.  
  1816. if self.mgr._tooBigMessage == BigMessage_Cut:
  1817.  
  1818. self.message(msg[:self.mgr._maxLength], html = html)
  1819.  
  1820. elif self.mgr._tooBigMessage == BigMessage_Multiple:
  1821.  
  1822. while len(msg) > 0:
  1823.  
  1824. sect = msg[:self.mgr._maxLength]
  1825.  
  1826. msg = msg[self.mgr._maxLength:]
  1827.  
  1828. self.message(sect, html = html)
  1829.  
  1830. return
  1831.  
  1832. msg = "<n" + self.user.nameColor + "/>" + msg
  1833.  
  1834. msg = "<f x%0.2i%s=\"%s\">" %(self.user.fontSize, self.user.fontColor, self.user.fontFace) + msg
  1835.  
  1836. self.rawMessage(msg)
  1837.  
  1838.  
  1839.  
  1840. def setBgMode(self, mode):
  1841.  
  1842. self._sendCommand("msgbg", str(mode))
  1843.  
  1844.  
  1845.  
  1846. def setRecordingMode(self, mode):
  1847.  
  1848. self._sendCommand("msgmedia", str(mode))
  1849.  
  1850.  
  1851.  
  1852. def addMod(self, user):
  1853.  
  1854. """
  1855.  
  1856. Add a moderator.
  1857.  
  1858.  
  1859.  
  1860. @type user: User
  1861.  
  1862. @param user: User to mod.
  1863.  
  1864. """
  1865.  
  1866. if self.getLevel(self.user) == 2:
  1867.  
  1868. self._sendCommand("addmod", user.name)
  1869.  
  1870.  
  1871.  
  1872. def removeMod(self, user):
  1873.  
  1874. """
  1875.  
  1876. Remove a moderator.
  1877.  
  1878.  
  1879.  
  1880. @type user: User
  1881.  
  1882. @param user: User to demod.
  1883.  
  1884. """
  1885.  
  1886. if self.getLevel(self.user) == 2:
  1887.  
  1888. self._sendCommand("removemod", user.name)
  1889.  
  1890.  
  1891.  
  1892. def flag(self, message):
  1893.  
  1894. """
  1895.  
  1896. Flag a message.
  1897.  
  1898.  
  1899.  
  1900. @type message: Message
  1901.  
  1902. @param message: message to flag
  1903.  
  1904. """
  1905.  
  1906. self._sendCommand("g_flag", message.msgid)
  1907.  
  1908.  
  1909.  
  1910. def flagUser(self, user):
  1911.  
  1912. """
  1913.  
  1914. Flag a user.
  1915.  
  1916.  
  1917.  
  1918. @type user: User
  1919.  
  1920. @param user: user to flag
  1921.  
  1922.  
  1923.  
  1924. @rtype: bool
  1925.  
  1926. @return: whether a message to flag was found
  1927.  
  1928. """
  1929.  
  1930. msg = self.getLastMessage(user)
  1931.  
  1932. if msg:
  1933.  
  1934. self.flag(msg)
  1935.  
  1936. return True
  1937.  
  1938. return False
  1939.  
  1940.  
  1941.  
  1942. def delete(self, message):
  1943.  
  1944. """
  1945.  
  1946. Delete a message. (Moderator only)
  1947.  
  1948.  
  1949.  
  1950. @type message: Message
  1951.  
  1952. @param message: message to delete
  1953.  
  1954. """
  1955.  
  1956. if self.getLevel(self.user) > 0:
  1957.  
  1958. self._sendCommand("delmsg", message.msgid)
  1959.  
  1960.  
  1961.  
  1962. def rawClearUser(self, unid):
  1963.  
  1964. self._sendCommand("delallmsg", unid)
  1965.  
  1966.  
  1967.  
  1968. def clearUser(self, user):
  1969.  
  1970. """
  1971.  
  1972. Clear all of a user's messages. (Moderator only)
  1973.  
  1974.  
  1975.  
  1976. @type user: User
  1977.  
  1978. @param user: user to delete messages of
  1979.  
  1980.  
  1981.  
  1982. @rtype: bool
  1983.  
  1984. @return: whether a message to delete was found
  1985.  
  1986. """
  1987.  
  1988. if self.getLevel(self.user) > 0:
  1989.  
  1990. msg = self.getLastMessage(user)
  1991.  
  1992. if msg:
  1993.  
  1994. self.rawClearUser(msg.unid)
  1995.  
  1996. return True
  1997.  
  1998. return False
  1999.  
  2000.  
  2001.  
  2002. def clearall(self):
  2003.  
  2004. """Clear all messages. (Owner only)"""
  2005.  
  2006. if self.getLevel(self.user) == 2:
  2007.  
  2008. self._sendCommand("clearall")
  2009.  
  2010.  
  2011.  
  2012. def rawBan(self, name, ip, unid):
  2013.  
  2014. """
  2015.  
  2016. Execute the block command using specified arguments.
  2017.  
  2018. (For advanced usage)
  2019.  
  2020.  
  2021.  
  2022. @type name: str
  2023.  
  2024. @param name: name
  2025.  
  2026. @type ip: str
  2027.  
  2028. @param ip: ip address
  2029.  
  2030. @type unid: str
  2031.  
  2032. @param unid: unid
  2033.  
  2034. """
  2035.  
  2036. self._sendCommand("block", unid, ip, name)
  2037.  
  2038.  
  2039.  
  2040. def ban(self, msg):
  2041.  
  2042. """
  2043.  
  2044. Ban a message's sender. (Moderator only)
  2045.  
  2046.  
  2047.  
  2048. @type message: Message
  2049.  
  2050. @param message: message to ban sender of
  2051.  
  2052. """
  2053.  
  2054. if self.getLevel(self.user) > 0:
  2055.  
  2056. self.rawBan(msg.user.name, msg.ip, msg.unid)
  2057.  
  2058.  
  2059.  
  2060. def banUser(self, user):
  2061.  
  2062. """
  2063.  
  2064. Ban a user. (Moderator only)
  2065.  
  2066.  
  2067.  
  2068. @type user: User
  2069.  
  2070. @param user: user to ban
  2071.  
  2072.  
  2073.  
  2074. @rtype: bool
  2075.  
  2076. @return: whether a message to ban the user was found
  2077.  
  2078. """
  2079.  
  2080. msg = self.getLastMessage(user)
  2081.  
  2082. if msg:
  2083.  
  2084. self.ban(msg)
  2085.  
  2086. return True
  2087.  
  2088. return False
  2089.  
  2090.  
  2091.  
  2092. def requestBanlist(self):
  2093.  
  2094. """Request an updated banlist."""
  2095.  
  2096. self._sendCommand("blocklist", "block", "", "next", "500")
  2097.  
  2098.  
  2099.  
  2100. def rawUnban(self, name, ip, unid):
  2101.  
  2102. """
  2103.  
  2104. Execute the unblock command using specified arguments.
  2105.  
  2106. (For advanced usage)
  2107.  
  2108.  
  2109.  
  2110. @type name: str
  2111.  
  2112. @param name: name
  2113.  
  2114. @type ip: str
  2115.  
  2116. @param ip: ip address
  2117.  
  2118. @type unid: str
  2119.  
  2120. @param unid: unid
  2121.  
  2122. """
  2123.  
  2124. self._sendCommand("removeblock", unid, ip, name)
  2125.  
  2126.  
  2127.  
  2128. def unban(self, user):
  2129.  
  2130. """
  2131.  
  2132. Unban a user. (Moderator only)
  2133.  
  2134.  
  2135.  
  2136. @type user: User
  2137.  
  2138. @param user: user to unban
  2139.  
  2140.  
  2141.  
  2142. @rtype: bool
  2143.  
  2144. @return: whether it succeeded
  2145.  
  2146. """
  2147.  
  2148. rec = self._getBanRecord(user)
  2149.  
  2150. if rec:
  2151.  
  2152. self.rawUnban(rec[2].name, rec[1], rec[0])
  2153.  
  2154. return True
  2155.  
  2156. else:
  2157.  
  2158. return False
  2159.  
  2160.  
  2161.  
  2162. ####
  2163.  
  2164. # Util
  2165.  
  2166. ####
  2167.  
  2168. def _getBanRecord(self, user):
  2169.  
  2170. for record in self._banlist:
  2171.  
  2172. if record[2] == user:
  2173.  
  2174. return record
  2175.  
  2176. return None
  2177.  
  2178.  
  2179.  
  2180. def _callEvent(self, evt, *args, **kw):
  2181.  
  2182. getattr(self.mgr, evt)(self, *args, **kw)
  2183.  
  2184. self.mgr.onEventCalled(self, evt, *args, **kw)
  2185.  
  2186.  
  2187.  
  2188. def _write(self, data):
  2189.  
  2190. if self._wlock:
  2191.  
  2192. self._wlockbuf += data
  2193.  
  2194. else:
  2195.  
  2196. self.mgr._write(self, data)
  2197.  
  2198.  
  2199.  
  2200. def _setWriteLock(self, lock):
  2201.  
  2202. self._wlock = lock
  2203.  
  2204. if self._wlock == False:
  2205.  
  2206. self._write(self._wlockbuf)
  2207.  
  2208. self._wlockbuf = b""
  2209.  
  2210.  
  2211.  
  2212. def _sendCommand(self, *args):
  2213.  
  2214. """
  2215.  
  2216. Send a command.
  2217.  
  2218.  
  2219.  
  2220. @type args: [str, str, ...]
  2221.  
  2222. @param args: command and list of arguments
  2223.  
  2224. """
  2225.  
  2226. if self._firstCommand:
  2227.  
  2228. terminator = b"\x00"
  2229.  
  2230. self._firstCommand = False
  2231.  
  2232. else:
  2233.  
  2234. terminator = b"\r\n\x00"
  2235.  
  2236. self._write(":".join(args).encode() + terminator)
  2237.  
  2238.  
  2239.  
  2240. def getLevel(self, user):
  2241.  
  2242. if user == self._owner: return 2
  2243.  
  2244. if user in self._mods: return 1
  2245.  
  2246. return 0
  2247.  
  2248.  
  2249.  
  2250. def getLastMessage(self, user = None):
  2251.  
  2252. if user:
  2253.  
  2254. try:
  2255.  
  2256. i = 1
  2257.  
  2258. while True:
  2259.  
  2260. msg = self._history[-i]
  2261.  
  2262. if msg.user == user:
  2263.  
  2264. return msg
  2265.  
  2266. i += 1
  2267.  
  2268. except IndexError:
  2269.  
  2270. return None
  2271.  
  2272. else:
  2273.  
  2274. try:
  2275.  
  2276. return self._history[-1]
  2277.  
  2278. except IndexError:
  2279.  
  2280. return None
  2281.  
  2282. return None
  2283.  
  2284.  
  2285.  
  2286. def findUser(self, name):
  2287.  
  2288. name = name.lower()
  2289.  
  2290. ul = self.getUserlist()
  2291.  
  2292. udi = dict(zip([u.name for u in ul], ul))
  2293.  
  2294. cname = None
  2295.  
  2296. for n in udi.keys():
  2297.  
  2298. if n.find(name) != -1:
  2299.  
  2300. if cname: return None #ambigious!!
  2301.  
  2302. cname = n
  2303.  
  2304. if cname: return udi[cname]
  2305.  
  2306. else: return None
  2307.  
  2308.  
  2309.  
  2310. ####
  2311.  
  2312. # History
  2313.  
  2314. ####
  2315.  
  2316. def _addHistory(self, msg):
  2317.  
  2318. """
  2319.  
  2320. Add a message to history.
  2321.  
  2322.  
  2323.  
  2324. @type msg: Message
  2325.  
  2326. @param msg: message
  2327.  
  2328. """
  2329.  
  2330. self._history.append(msg)
  2331.  
  2332. if len(self._history) > self.mgr._maxHistoryLength:
  2333.  
  2334. rest, self._history = self._history[:-self.mgr._maxHistoryLength], self._history[-self.mgr._maxHistoryLength:]
  2335.  
  2336. for msg in rest: msg.detach()
  2337.  
  2338.  
  2339.  
  2340. ################################################################
  2341.  
  2342. # RoomManager class
  2343.  
  2344. ################################################################
  2345.  
  2346. class RoomManager(object):
  2347.  
  2348. """Class that manages multiple connections."""
  2349.  
  2350. ####
  2351.  
  2352. # Config
  2353.  
  2354. ####
  2355.  
  2356. _Room = Room
  2357.  
  2358. _PM = PM
  2359.  
  2360. _PMHost = "c1.chatango.com"
  2361.  
  2362. _PMPort = 5222
  2363.  
  2364. _TimerResolution = 0.2 #at least x times per second
  2365.  
  2366. _pingDelay = 20
  2367.  
  2368. _userlistMode = Userlist_Recent
  2369.  
  2370. _userlistUnique = True
  2371.  
  2372. _userlistMemory = 50
  2373.  
  2374. _userlistEventUnique = False
  2375.  
  2376. _tooBigMessage = BigMessage_Multiple
  2377.  
  2378. _maxLength = 1800
  2379.  
  2380. _maxHistoryLength = 150
  2381.  
  2382.  
  2383.  
  2384. ####
  2385.  
  2386. # Init
  2387.  
  2388. ####
  2389.  
  2390. def __init__(self, name = None, password = None, pm = True):
  2391.  
  2392. self._name = name
  2393.  
  2394. self._password = password
  2395.  
  2396. self._running = False
  2397.  
  2398. self._tasks = set()
  2399.  
  2400. self._rooms = dict()
  2401.  
  2402. if pm:
  2403.  
  2404. self._pm = self._PM(mgr = self)
  2405.  
  2406. else:
  2407.  
  2408. self._pm = None
  2409.  
  2410.  
  2411.  
  2412. ####
  2413.  
  2414. # Join/leave
  2415.  
  2416. ####
  2417.  
  2418. def joinRoom(self, room):
  2419.  
  2420. """
  2421.  
  2422. Join a room or return None if already joined.
  2423.  
  2424.  
  2425.  
  2426. @type room: str
  2427.  
  2428. @param room: room to join
  2429.  
  2430.  
  2431.  
  2432. @rtype: Room or None
  2433.  
  2434. @return: the room or nothing
  2435.  
  2436. """
  2437.  
  2438. room = room.lower()
  2439.  
  2440. if room not in self._rooms:
  2441.  
  2442. con = self._Room(room, mgr = self)
  2443.  
  2444. self._rooms[room] = con
  2445.  
  2446. return con
  2447.  
  2448. else:
  2449.  
  2450. return None
  2451.  
  2452.  
  2453.  
  2454. def leaveRoom(self, room):
  2455.  
  2456. """
  2457.  
  2458. Leave a room.
  2459.  
  2460.  
  2461.  
  2462. @type room: str
  2463.  
  2464. @param room: room to leave
  2465.  
  2466. """
  2467.  
  2468. room = room.lower()
  2469.  
  2470. if room in self._rooms:
  2471.  
  2472. con = self._rooms[room]
  2473.  
  2474. con.disconnect()
  2475.  
  2476.  
  2477.  
  2478. def getRoom(self, room):
  2479.  
  2480. """
  2481.  
  2482. Get room with a name, or None if not connected to this room.
  2483.  
  2484.  
  2485.  
  2486. @type room: str
  2487.  
  2488. @param room: room
  2489.  
  2490.  
  2491.  
  2492. @rtype: Room
  2493.  
  2494. @return: the room
  2495.  
  2496. """
  2497.  
  2498. room = room
  2499.  
  2500. if room in self._rooms:
  2501.  
  2502. return self._rooms[room]
  2503.  
  2504. else:
  2505.  
  2506. return None
  2507.  
  2508.  
  2509.  
  2510. ####
  2511.  
  2512. # Properties
  2513.  
  2514. ####
  2515.  
  2516. def getUser(self): return User(self._name)
  2517.  
  2518. def getName(self): return self._name
  2519.  
  2520. def getPassword(self): return self._password
  2521.  
  2522. def getRooms(self): return set(self._rooms.values())
  2523.  
  2524. def getRoomNames(self): return set(self._rooms.keys())
  2525.  
  2526. def getPM(self): return self._pm
  2527.  
  2528.  
  2529.  
  2530. user = property(getUser)
  2531.  
  2532. name = property(getName)
  2533.  
  2534. password = property(getPassword)
  2535.  
  2536. rooms = property(getRooms)
  2537.  
  2538. roomnames = property(getRoomNames)
  2539.  
  2540. pm = property(getPM)
  2541.  
  2542.  
  2543.  
  2544. ####
  2545.  
  2546. # Virtual methods
  2547.  
  2548. ####
  2549.  
  2550. def onInit(self):
  2551.  
  2552. """Called on init."""
  2553.  
  2554. pass
  2555.  
  2556.  
  2557.  
  2558. def onConnect(self, room):
  2559.  
  2560. """
  2561.  
  2562. Called when connected to the room.
  2563.  
  2564.  
  2565.  
  2566. @type room: Room
  2567.  
  2568. @param room: room where the event occured
  2569.  
  2570. """
  2571.  
  2572. pass
  2573.  
  2574.  
  2575.  
  2576. def onReconnect(self, room):
  2577.  
  2578. """
  2579.  
  2580. Called when reconnected to the room.
  2581.  
  2582.  
  2583.  
  2584. @type room: Room
  2585.  
  2586. @param room: room where the event occured
  2587.  
  2588. """
  2589.  
  2590. pass
  2591.  
  2592.  
  2593.  
  2594. def onConnectFail(self, room):
  2595.  
  2596. """
  2597.  
  2598. Called when the connection failed.
  2599.  
  2600.  
  2601.  
  2602. @type room: Room
  2603.  
  2604. @param room: room where the event occured
  2605.  
  2606. """
  2607.  
  2608. pass
  2609.  
  2610.  
  2611.  
  2612. def onDisconnect(self, room):
  2613.  
  2614. """
  2615.  
  2616. Called when the client gets disconnected.
  2617.  
  2618.  
  2619.  
  2620. @type room: Room
  2621.  
  2622. @param room: room where the event occured
  2623.  
  2624. """
  2625.  
  2626. pass
  2627.  
  2628.  
  2629.  
  2630. def onLoginFail(self, room):
  2631.  
  2632. """
  2633.  
  2634. Called on login failure, disconnects after.
  2635.  
  2636.  
  2637.  
  2638. @type room: Room
  2639.  
  2640. @param room: room where the event occured
  2641.  
  2642. """
  2643.  
  2644. pass
  2645.  
  2646.  
  2647.  
  2648. def onFloodBan(self, room):
  2649.  
  2650. """
  2651.  
  2652. Called when either flood banned or flagged.
  2653.  
  2654.  
  2655.  
  2656. @type room: Room
  2657.  
  2658. @param room: room where the event occured
  2659.  
  2660. """
  2661.  
  2662. pass
  2663.  
  2664.  
  2665.  
  2666. def onFloodBanRepeat(self, room):
  2667.  
  2668. """
  2669.  
  2670. Called when trying to send something when floodbanned.
  2671.  
  2672.  
  2673.  
  2674. @type room: Room
  2675.  
  2676. @param room: room where the event occured
  2677.  
  2678. """
  2679.  
  2680. pass
  2681.  
  2682.  
  2683.  
  2684. def onFloodWarning(self, room):
  2685.  
  2686. """
  2687.  
  2688. Called when an overflow warning gets received.
  2689.  
  2690.  
  2691.  
  2692. @type room: Room
  2693.  
  2694. @param room: room where the event occured
  2695.  
  2696. """
  2697.  
  2698. pass
  2699.  
  2700.  
  2701.  
  2702. def onMessageDelete(self, room, user, message):
  2703.  
  2704. """
  2705.  
  2706. Called when a message gets deleted.
  2707.  
  2708.  
  2709.  
  2710. @type room: Room
  2711.  
  2712. @param room: room where the event occured
  2713.  
  2714. @type user: User
  2715.  
  2716. @param user: owner of deleted message
  2717.  
  2718. @type message: Message
  2719.  
  2720. @param message: message that got deleted
  2721.  
  2722. """
  2723.  
  2724. pass
  2725.  
  2726.  
  2727.  
  2728. def onModChange(self, room):
  2729.  
  2730. """
  2731.  
  2732. Called when the moderator list changes.
  2733.  
  2734.  
  2735.  
  2736. @type room: Room
  2737.  
  2738. @param room: room where the event occured
  2739.  
  2740. """
  2741.  
  2742. pass
  2743.  
  2744.  
  2745.  
  2746. def onModAdd(self, room, user):
  2747.  
  2748. """
  2749.  
  2750. Called when a moderator gets added.
  2751.  
  2752.  
  2753.  
  2754. @type room: Room
  2755.  
  2756. @param room: room where the event occured
  2757.  
  2758. """
  2759.  
  2760. pass
  2761.  
  2762.  
  2763.  
  2764. def onModRemove(self, room, user):
  2765.  
  2766. """
  2767.  
  2768. Called when a moderator gets removed.
  2769.  
  2770.  
  2771.  
  2772. @type room: Room
  2773.  
  2774. @param room: room where the event occured
  2775.  
  2776. """
  2777.  
  2778. pass
  2779.  
  2780.  
  2781.  
  2782. def onMessage(self, room, user, message):
  2783.  
  2784. """
  2785.  
  2786. Called when a message gets received.
  2787.  
  2788.  
  2789.  
  2790. @type room: Room
  2791.  
  2792. @param room: room where the event occured
  2793.  
  2794. @type user: User
  2795.  
  2796. @param user: owner of message
  2797.  
  2798. @type message: Message
  2799.  
  2800. @param message: received message
  2801.  
  2802. """
  2803.  
  2804. pass
  2805.  
  2806.  
  2807.  
  2808. def onHistoryMessage(self, room, user, message):
  2809.  
  2810. """
  2811.  
  2812. Called when a message gets received from history.
  2813.  
  2814.  
  2815.  
  2816. @type room: Room
  2817.  
  2818. @param room: room where the event occured
  2819.  
  2820. @type user: User
  2821.  
  2822. @param user: owner of message
  2823.  
  2824. @type message: Message
  2825.  
  2826. @param message: the message that got added
  2827.  
  2828. """
  2829.  
  2830. pass
  2831.  
  2832.  
  2833.  
  2834. def onJoin(self, room, user):
  2835.  
  2836. """
  2837.  
  2838. Called when a user joins. Anonymous users get ignored here.
  2839.  
  2840.  
  2841.  
  2842. @type room: Room
  2843.  
  2844. @param room: room where the event occured
  2845.  
  2846. @type user: User
  2847.  
  2848. @param user: the user that has joined
  2849.  
  2850. """
  2851.  
  2852. pass
  2853.  
  2854.  
  2855.  
  2856. def onLeave(self, room, user):
  2857.  
  2858. """
  2859.  
  2860. Called when a user leaves. Anonymous users get ignored here.
  2861.  
  2862.  
  2863.  
  2864. @type room: Room
  2865.  
  2866. @param room: room where the event occured
  2867.  
  2868. @type user: User
  2869.  
  2870. @param user: the user that has left
  2871.  
  2872. """
  2873.  
  2874. pass
  2875.  
  2876.  
  2877.  
  2878. def onRaw(self, room, raw):
  2879.  
  2880. """
  2881.  
  2882. Called before any command parsing occurs.
  2883.  
  2884.  
  2885.  
  2886. @type room: Room
  2887.  
  2888. @param room: room where the event occured
  2889.  
  2890. @type raw: str
  2891.  
  2892. @param raw: raw command data
  2893.  
  2894. """
  2895.  
  2896. pass
  2897.  
  2898.  
  2899.  
  2900. def onPing(self, room):
  2901.  
  2902. """
  2903.  
  2904. Called when a ping gets sent.
  2905.  
  2906.  
  2907.  
  2908. @type room: Room
  2909.  
  2910. @param room: room where the event occured
  2911.  
  2912. """
  2913.  
  2914. pass
  2915.  
  2916.  
  2917.  
  2918. def onUserCountChange(self, room):
  2919.  
  2920. """
  2921.  
  2922. Called when the user count changes.
  2923.  
  2924.  
  2925.  
  2926. @type room: Room
  2927.  
  2928. @param room: room where the event occured
  2929.  
  2930. """
  2931.  
  2932. pass
  2933.  
  2934.  
  2935.  
  2936. def onBan(self, room, user, target):
  2937.  
  2938. """
  2939.  
  2940. Called when a user gets banned.
  2941.  
  2942.  
  2943.  
  2944. @type room: Room
  2945.  
  2946. @param room: room where the event occured
  2947.  
  2948. @type user: User
  2949.  
  2950. @param user: user that banned someone
  2951.  
  2952. @type target: User
  2953.  
  2954. @param target: user that got banned
  2955.  
  2956. """
  2957.  
  2958. pass
  2959.  
  2960.  
  2961.  
  2962. def onUnban(self, room, user, target):
  2963.  
  2964. """
  2965.  
  2966. Called when a user gets unbanned.
  2967.  
  2968.  
  2969.  
  2970. @type room: Room
  2971.  
  2972. @param room: room where the event occured
  2973.  
  2974. @type user: User
  2975.  
  2976. @param user: user that unbanned someone
  2977.  
  2978. @type target: User
  2979.  
  2980. @param target: user that got unbanned
  2981.  
  2982. """
  2983.  
  2984. pass
  2985.  
  2986.  
  2987.  
  2988. def onBanlistUpdate(self, room):
  2989.  
  2990. """
  2991.  
  2992. Called when a banlist gets updated.
  2993.  
  2994.  
  2995.  
  2996. @type room: Room
  2997.  
  2998. @param room: room where the event occured
  2999.  
  3000. """
  3001.  
  3002. pass
  3003.  
  3004.  
  3005.  
  3006. def onPMConnect(self, pm):
  3007.  
  3008. pass
  3009.  
  3010.  
  3011.  
  3012. def onPMDisconnect(self, pm):
  3013.  
  3014. pass
  3015.  
  3016.  
  3017.  
  3018. def onPMPing(self, pm):
  3019.  
  3020. pass
  3021.  
  3022.  
  3023.  
  3024. def onPMMessage(self, pm, user, body):
  3025.  
  3026. pass
  3027.  
  3028.  
  3029.  
  3030. def onPMOfflineMessage(self, pm, user, body):
  3031.  
  3032. pass
  3033.  
  3034.  
  3035.  
  3036. def onPMContactlistReceive(self, pm):
  3037.  
  3038. pass
  3039.  
  3040.  
  3041.  
  3042. def onPMBlocklistReceive(self, pm):
  3043.  
  3044. pass
  3045.  
  3046.  
  3047.  
  3048. def onPMContactAdd(self, pm, user):
  3049.  
  3050. pass
  3051.  
  3052.  
  3053.  
  3054. def onPMContactRemove(self, pm, user):
  3055.  
  3056. pass
  3057.  
  3058.  
  3059.  
  3060. def onPMBlock(self, pm, user):
  3061.  
  3062. pass
  3063.  
  3064.  
  3065.  
  3066. def onPMUnblock(self, pm, user):
  3067.  
  3068. pass
  3069.  
  3070.  
  3071.  
  3072. def onPMContactOnline(self, pm, user):
  3073.  
  3074. pass
  3075.  
  3076.  
  3077.  
  3078. def onPMContactOffline(self, pm, user):
  3079.  
  3080. pass
  3081.  
  3082.  
  3083.  
  3084. def onEventCalled(self, room, evt, *args, **kw):
  3085.  
  3086. """
  3087.  
  3088. Called on every room-based event.
  3089.  
  3090.  
  3091.  
  3092. @type room: Room
  3093.  
  3094. @param room: room where the event occured
  3095.  
  3096. @type evt: str
  3097.  
  3098. @param evt: the event
  3099.  
  3100. """
  3101.  
  3102. pass
  3103.  
  3104.  
  3105.  
  3106. ####
  3107.  
  3108. # Deferring
  3109.  
  3110. ####
  3111. def deferToThread(self, callback, func, *args, **kw):
  3112. """
  3113. Defer a function to a thread and callback the return value.
  3114. @type callback: function
  3115. @param callback: function to call on completion
  3116. @type cbargs: tuple or list
  3117. @param cbargs: arguments to get supplied to the callback
  3118. @type func: function
  3119. @param func: function to call
  3120. """
  3121. def f(func, callback, *args, **kw):
  3122. ret = func(*args, **kw)
  3123. self.setTimeout(0, callback, ret)
  3124. threading._start_new_thread(f, (func, callback) + args, kw)
  3125. ####
  3126. # Scheduling
  3127. ####
  3128. class _Task(object):
  3129. def cancel(self):
  3130. """Sugar for removeTask."""
  3131. self.mgr.removeTask(self)
  3132. def _tick(self):
  3133. now = time.time()
  3134. for task in set(self._tasks):
  3135. if task.target <= now:
  3136. task.func(*task.args, **task.kw)
  3137. if task.isInterval:
  3138. task.target = now + task.timeout
  3139. else:
  3140. self._tasks.remove(task)
  3141. def setTimeout(self, timeout, func, *args, **kw):
  3142. """
  3143. Call a function after at least timeout seconds with specified arguments.
  3144. @type timeout: int
  3145. @param timeout: timeout
  3146. @type func: function
  3147. @param func: function to call
  3148. @rtype: _Task
  3149. @return: object representing the task
  3150. """
  3151. task = self._Task()
  3152. task.mgr = self
  3153. task.target = time.time() + timeout
  3154. task.timeout = timeout
  3155. task.func = func
  3156. task.isInterval = False
  3157. task.args = args
  3158. task.kw = kw
  3159. self._tasks.add(task)
  3160. return task
  3161. def setInterval(self, timeout, func, *args, **kw):
  3162. """
  3163. Call a function at least every timeout seconds with specified arguments.
  3164. @type timeout: int
  3165. @param timeout: timeout
  3166. @type func: function
  3167. @param func: function to call
  3168. @rtype: _Task
  3169. @return: object representing the task
  3170. """
  3171. task = self._Task()
  3172. task.mgr = self
  3173. task.target = time.time() + timeout
  3174. task.timeout = timeout
  3175. task.func = func
  3176. task.isInterval = True
  3177. task.args = args
  3178. task.kw = kw
  3179. self._tasks.add(task)
  3180. return task
  3181. def removeTask(self, task):
  3182. """
  3183. Cancel a task.
  3184. @type task: _Task
  3185. @param task: task to cancel
  3186. """
  3187. self._tasks.remove(task)
  3188. ####
  3189. # Util
  3190. ####
  3191. def _write(self, room, data):
  3192. room._wbuf += data
  3193. def getConnections(self):
  3194. li = list(self._rooms.values())
  3195. if self._pm:
  3196. li.append(self._pm)
  3197. return [c for c in li if c._sock != None]
  3198. ####
  3199. # Main
  3200. ####
  3201. def main(self):
  3202. self.onInit()
  3203. self._running = True
  3204. while self._running:
  3205. conns = self.getConnections()
  3206. socks = [x._sock for x in conns]
  3207. wsocks = [x._sock for x in conns if x._wbuf != b""]
  3208. rd, wr, sp = select.select(socks, wsocks, [], self._TimerResolution)
  3209. for sock in rd:
  3210. con = [c for c in conns if c._sock == sock][0]
  3211. try:
  3212. data = sock.recv(1024)
  3213. if(len(data) > 0):
  3214. con._feed(data)
  3215. else:
  3216. con.disconnect()
  3217. except socket.error:
  3218. pass
  3219. for sock in wr:
  3220. con = [c for c in conns if c._sock == sock][0]
  3221. try:
  3222. size = sock.send(con._wbuf)
  3223. con._wbuf = con._wbuf[size:]
  3224. except socket.error:
  3225. pass
  3226. self._tick()
  3227. @classmethod
  3228. def easy_start(cl, rooms = None, name = None, password = None, pm = True):
  3229. """
  3230. Prompts the user for missing info, then starts.
  3231. @type rooms: list
  3232. @param room: rooms to join
  3233. @type name: str
  3234. @param name: name to join as ("" = None, None = unspecified)
  3235. @type password: str
  3236. @param password: password to join with ("" = None, None = unspecified)
  3237. """
  3238. if not rooms: rooms = str(input("Room names separated by semicolons: ")).split(";")
  3239. if len(rooms) == 1 and rooms[0] == "": rooms = []
  3240. if not name: name = str(input("User name: "))
  3241. if name == "": name = None
  3242. if not password: password = str(input("User password: "))
  3243. if password == "": password = None
  3244. self = cl(name, password, pm = pm)
  3245. for room in rooms:
  3246. self.joinRoom(room)
  3247. self.main()
  3248. def stop(self):
  3249. for conn in list(self._rooms.values()):
  3250. conn.disconnect()
  3251. self._running = False
  3252. ####
  3253. # Commands
  3254. ####
  3255. def enableBg(self):
  3256. """Enable background if available."""
  3257. self.user._mbg = True
  3258. for room in self.rooms:
  3259. room.setBgMode(1)
  3260. def disableBg(self):
  3261. """Disable background."""
  3262. self.user._mbg = False
  3263. for room in self.rooms:
  3264. room.setBgMode(0)
  3265. def enableRecording(self):
  3266. """Enable recording if available."""
  3267. self.user._mrec = True
  3268. for room in self.rooms:
  3269. room.setRecordingMode(1)
  3270. def disableRecording(self):
  3271. """Disable recording."""
  3272. self.user._mrec = False
  3273. for room in self.rooms:
  3274. room.setRecordingMode(0)
  3275. def setNameColor(self, color3x):
  3276. """
  3277. Set name color.
  3278. @type color3x: str
  3279. @param color3x: a 3-char RGB hex code for the color
  3280. """
  3281. self.user._nameColor = color3x
  3282. def setFontColor(self, color3x):
  3283. """
  3284. Set font color.
  3285. @type color3x: str
  3286. @param color3x: a 3-char RGB hex code for the color
  3287. """
  3288. self.user._fontColor = color3x
  3289. def setFontFace(self, face):
  3290. """
  3291. Set font face/family.
  3292. @type face: str
  3293. @param face: the font face
  3294. """
  3295. self.user._fontFace = face
  3296. def setFontSize(self, size):
  3297. """
  3298. Set font size.
  3299. @type size: int
  3300. @param size: the font size (limited: 9 to 22)
  3301. """
  3302. if size < 9: size = 9
  3303. if size > 22: size = 22
  3304. self.user._fontSize = size
  3305. ################################################################
  3306. # User class (well, yeah, i lied, it's actually _User)
  3307. ################################################################
  3308. _users = dict()
  3309. def User(name, *args, **kw):
  3310. name = name.lower()
  3311. user = _users.get(name)
  3312. if not user:
  3313. user = _User(name = name, *args, **kw)
  3314. _users[name] = user
  3315. return user
  3316. class _User(object):
  3317. """Class that represents a user."""
  3318. ####
  3319. # Init
  3320. ####
  3321. def __init__(self, name, **kw):
  3322. self._name = name.lower()
  3323. self._sids = dict()
  3324. self._msgs = list()
  3325. self._nameColor = "000"
  3326. self._fontSize = 12
  3327. self._fontFace = "0"
  3328. self._fontColor = "000"
  3329. self._mbg = False
  3330. self._mrec = False
  3331. for attr, val in kw.items():
  3332. if val == None: continue
  3333. setattr(self, "_" + attr, val)
  3334. ####
  3335. # Properties
  3336. ####
  3337. def getName(self): return self._name
  3338. def getSessionIds(self, room = None):
  3339. if room:
  3340. return self._sids.get(room, set())
  3341. else:
  3342. return set.union(*self._sids.values())
  3343. def getRooms(self): return self._sids.keys()
  3344. def getRoomNames(self): return [room.name for room in self.getRooms()]
  3345. def getFontColor(self): return self._fontColor
  3346. def getFontFace(self): return self._fontFace
  3347. def getFontSize(self): return self._fontSize
  3348. def getNameColor(self): return self._nameColor
  3349. name = property(getName)
  3350. sessionids = property(getSessionIds)
  3351. rooms = property(getRooms)
  3352. roomnames = property(getRoomNames)
  3353. fontColor = property(getFontColor)
  3354. fontFace = property(getFontFace)
  3355. fontSize = property(getFontSize)
  3356. nameColor = property(getNameColor)
  3357. ####
  3358. # Util
  3359. ####
  3360. def addSessionId(self, room, sid):
  3361. if room not in self._sids:
  3362. self._sids[room] = set()
  3363. self._sids[room].add(sid)
  3364. def removeSessionId(self, room, sid):
  3365. try:
  3366. self._sids[room].remove(sid)
  3367. if len(self._sids[room]) == 0:
  3368. del self._sids[room]
  3369. except KeyError:
  3370. pass
  3371. def clearSessionIds(self, room):
  3372. try:
  3373. del self._sids[room]
  3374. except KeyError:
  3375. pass
  3376. def hasSessionId(self, room, sid):
  3377. try:
  3378. if sid in self._sids[room]:
  3379. return True
  3380. else:
  3381. return False
  3382. except KeyError:
  3383. return False
  3384. ####
  3385. # Repr
  3386. ####
  3387. def __repr__(self):
  3388. return "<User: %s>" %(self.name)
  3389. ################################################################
  3390. # Message class
  3391. ################################################################
  3392. class Message(object):
  3393. """Class that represents a message."""
  3394. ####
  3395. # Attach/detach
  3396. ####
  3397. def attach(self, room, msgid):
  3398. """
  3399. Attach the Message to a message id.
  3400. @type msgid: str
  3401. @param msgid: message id
  3402. """
  3403. if self._msgid == None:
  3404. self._room = room
  3405. self._msgid = msgid
  3406. self._room._msgs[msgid] = self
  3407. def detach(self):
  3408. """Detach the Message."""
  3409. if self._msgid != None and self._msgid in self._room._msgs:
  3410. del self._room._msgs[self._msgid]
  3411. self._msgid = None
  3412. ####
  3413. # Init
  3414. ####
  3415. def __init__(self, **kw):
  3416. self._msgid = None
  3417. self._time = None
  3418. self._user = None
  3419. self._body = None
  3420. self._room = None
  3421. self._raw = ""
  3422. self._ip = None
  3423. self._unid = ""
  3424. self._nameColor = "000"
  3425. self._fontSize = 12
  3426. self._fontFace = "0"
  3427. self._fontColor = "000"
  3428. for attr, val in kw.items():
  3429. if val == None: continue
  3430. setattr(self, "_" + attr, val)
  3431. ####
  3432. # Properties
  3433. ####
  3434. def getId(self): return self._msgid
  3435. def getTime(self): return self._time
  3436. def getUser(self): return self._user
  3437. def getBody(self): return self._body
  3438. def getUid(self): return self._uid
  3439. def getIP(self): return self._ip
  3440. def getFontColor(self): return self._fontColor
  3441. def getFontFace(self): return self._fontFace
  3442. def getFontSize(self): return self._fontSize
  3443. def getNameColor(self): return self._nameColor
  3444. def getRoom(self): return self._room
  3445. def getRaw(self): return self._raw
  3446. def getUnid(self): return self._unid
  3447. msgid = property(getId)
  3448. time = property(getTime)
  3449. user = property(getUser)
  3450. body = property(getBody)
  3451. uid = property(getUid)
  3452. room = property(getRoom)
  3453. ip = property(getIP)
  3454. fontColor = property(getFontColor)
  3455. fontFace = property(getFontFace)
  3456. fontSize = property(getFontSize)
  3457. raw = property(getRaw)
  3458. nameColor = property(getNameColor)
  3459. unid = property(getUnid)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement