Advertisement
Lyend

Untitled

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