Advertisement
Guest User

Untitled

a guest
Nov 19th, 2016
155
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 63.78 KB | None | 0 0
  1. ################################################################
  2. # File: ch.py
  3. # Title: Chatango Library
  4. # Original Author: Lumirayz/Lumz <lumirayz@gmail.com>
  5. # Current Maintainers and Contributors:
  6. # Nullspeaker <import codecs;codecs.encode('aunzzbaq129@tznvy.pbz','rot_13')>
  7. # asl97 <asl97@outlook.com>
  8. # pystub
  9. # dani87
  10. # domzy
  11. # kamijoutouma
  12. # piks
  13. # Version: 1.3.8
  14. # Description:
  15. # An event-based library for connecting to one or multiple Chatango rooms, has
  16. # support for several things including: messaging, message font,
  17. # name color, deleting, banning, recent history, 2 userlist modes,
  18. # flagging, avoiding flood bans, detecting flags.
  19. # Contact Info:
  20. # Any question, comment, or suggestion should be directed to the current
  21. # maintainers and contributors, located at:
  22. # https://github.com/Nullspeaker/ch.py
  23. # Where a more satisfactory response to all bug reports (which can be made on the
  24. # issues page) and other statements can be garnered. For things not specific or
  25. # in direct reference to this library, 'ch.py', a direct response can be filed
  26. # to the individual persons listed above as 'Current Maintainers and Contributors.'
  27. ################################################################
  28.  
  29. ################################################################
  30. # License
  31. ################################################################
  32. # Copyright 2011 Lumirayz
  33. # This program is distributed under the terms of the GNU GPL.
  34.  
  35. ################################################################
  36. # Imports
  37. ################################################################
  38. import socket
  39. import threading
  40. import time
  41. import random
  42. import re
  43. import sys
  44. import select
  45.  
  46. ################################################################
  47. # Debug stuff
  48. ################################################################
  49. debug = False
  50.  
  51. ################################################################
  52. # Python 2 compatibility
  53. ################################################################
  54. if sys.version_info[0] < 3:
  55. class urllib:
  56. parse = __import__("urllib")
  57. request = __import__("urllib2")
  58. input = raw_input
  59. import codecs
  60. import Queue as queue
  61. else:
  62. import queue
  63. import urllib.request
  64. import urllib.parse
  65.  
  66. ################################################################
  67. # Constants
  68. ################################################################
  69. Userlist_Recent = 0
  70. Userlist_All = 1
  71.  
  72. BigMessage_Multiple = 0
  73. BigMessage_Cut = 1
  74.  
  75. # minimum of 1 thread needed
  76. Number_of_Threads = 1
  77. ################################################################
  78. # Struct class
  79. ################################################################
  80. class Struct:
  81. def __init__(self, **entries):
  82. self.__dict__.update(entries)
  83.  
  84. ################################################################
  85. # Tagserver stuff
  86. ################################################################
  87. specials = {'mitvcanal': 56, 'animeultimacom': 34, 'cricket365live': 21, 'pokemonepisodeorg': 22, 'animelinkz': 20, 'sport24lt': 56, 'narutowire': 10, 'watchanimeonn': 22, 'cricvid-hitcric-': 51, 'narutochatt': 70, 'leeplarp': 27, 'stream2watch3': 56, 'ttvsports': 56, 'ver-anime': 8, 'vipstand': 21, 'eafangames': 56, 'soccerjumbo': 21, 'myfoxdfw': 67, 'kiiiikiii': 21, 'de-livechat': 5, 'rgsmotrisport': 51, 'dbzepisodeorg': 10, 'watch-dragonball': 8, 'peliculas-flv': 69, 'tvanimefreak': 54, 'tvtvanimefreak': 54}
  88. 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]]
  89.  
  90. def getServer(group):
  91. """
  92. Get the server host for a certain room.
  93.  
  94. @type group: str
  95. @param group: room name
  96.  
  97. @rtype: str
  98. @return: the server's hostname
  99. """
  100. try:
  101. sn = specials[group]
  102. except KeyError:
  103. group = group.replace("_", "q")
  104. group = group.replace("-", "q")
  105. fnv = float(int(group[0:min(5, len(group))], 36))
  106. lnv = group[6: (6 + min(3, len(group) - 5))]
  107. if(lnv):
  108. lnv = float(int(lnv, 36))
  109. lnv = max(lnv,1000)
  110. else:
  111. lnv = 1000
  112. num = (fnv % lnv) / lnv
  113. maxnum = sum(map(lambda x: x[1], tsweights))
  114. cumfreq = 0
  115. sn = 0
  116. for wgt in tsweights:
  117. cumfreq += float(wgt[1]) / maxnum
  118. if(num <= cumfreq):
  119. sn = int(wgt[0])
  120. break
  121. return "s" + str(sn) + ".chatango.com"
  122.  
  123. ################################################################
  124. # Uid
  125. ################################################################
  126. def _genUid():
  127. """
  128. generate a uid
  129. """
  130. return str(random.randrange(10 ** 15, 10 ** 16))
  131.  
  132. ################################################################
  133. # Message stuff
  134. ################################################################
  135. def _clean_message(msg):
  136. """
  137. Clean a message and return the message, n tag and f tag.
  138.  
  139. @type msg: str
  140. @param msg: the message
  141.  
  142. @rtype: str, str, str
  143. @returns: cleaned message, n tag contents, f tag contents
  144. """
  145. n = re.search("<n(.*?)/>", msg)
  146. if n: n = n.group(1)
  147. f = re.search("<f(.*?)>", msg)
  148. if f: f = f.group(1)
  149. msg = re.sub("<n.*?/>", "", msg)
  150. msg = re.sub("<f.*?>", "", msg)
  151. msg = _strip_html(msg)
  152. msg = msg.replace("&lt;", "<")
  153. msg = msg.replace("&gt;", ">")
  154. msg = msg.replace("&quot;", "\"")
  155. msg = msg.replace("&apos;", "'")
  156. msg = msg.replace("&amp;", "&")
  157. return msg, n, f
  158.  
  159. def _strip_html(msg):
  160. """Strip HTML."""
  161. li = msg.split("<")
  162. if len(li) == 1:
  163. return li[0]
  164. else:
  165. ret = list()
  166. for data in li:
  167. data = data.split(">", 1)
  168. if len(data) == 1:
  169. ret.append(data[0])
  170. elif len(data) == 2:
  171. ret.append(data[1])
  172. return "".join(ret)
  173.  
  174. def _parseNameColor(n):
  175. """This just returns its argument, should return the name color."""
  176. #probably is already the name
  177. return n
  178.  
  179. def _parseFont(f):
  180. """Parses the contents of a f tag and returns color, face and size."""
  181. #' xSZCOL="FONT"'
  182. try: #TODO: remove quick hack
  183. sizecolor, fontface = f.split("=", 1)
  184. sizecolor = sizecolor.strip()
  185. size = int(sizecolor[1:3])
  186. col = sizecolor[3:6]
  187. if col == "": col = None
  188. face = f.split("\"", 2)[1]
  189. return col, face, size
  190. except:
  191. return None, None, None
  192.  
  193. ################################################################
  194. # Anon id
  195. ################################################################
  196. def _getAnonId(n, ssid):
  197. """Gets the anon's id."""
  198. if n == None: n = "5504"
  199. try:
  200. return "".join(list(
  201. map(lambda x: str(x[0] + x[1])[-1], list(zip(
  202. list(map(lambda x: int(x), n)),
  203. list(map(lambda x: int(x), ssid[4:]))
  204. )))
  205. ))
  206. except ValueError:
  207. return "NNNN"
  208.  
  209. ################################################################
  210. # ANON PM class
  211. ################################################################
  212.  
  213. class _ANON_PM_OBJECT:
  214. """Manages connection with Chatango anon PM."""
  215. def __init__(self, mgr, name):
  216. self._connected = False
  217. self._mgr = mgr
  218. self._wlock = False
  219. self._firstCommand = True
  220. self._wbuf = b""
  221. self._wlockbuf = b""
  222. self._rbuf = b""
  223. self._pingTask = None
  224. self._name = name
  225.  
  226. def _auth(self):
  227. self._sendCommand("mhs","mini","unknown","%s" % (self._name))
  228. self._setWriteLock(True)
  229. return True
  230.  
  231. def disconnect(self):
  232. """Disconnect the bot from PM"""
  233. self._disconnect()
  234. self._callEvent("onAnonPMDisconnect", User(self._name))
  235.  
  236. def _disconnect(self):
  237. self._connected = False
  238. self._sock.close()
  239. self._sock = None
  240.  
  241. def ping(self):
  242. """send a ping"""
  243. self._sendCommand("")
  244. self._callEvent("onPMPing")
  245.  
  246. def message(self, user, msg):
  247. """send a pm to a user"""
  248. if msg!=None:
  249. self._sendCommand("msg", user.name, msg)
  250.  
  251. ####
  252. # Feed
  253. ####
  254. def _feed(self, data):
  255. """
  256. Feed data to the connection.
  257.  
  258. @type data: bytes
  259. @param data: data to be fed
  260. """
  261. self._rbuf += data
  262. while self._rbuf.find(b"\x00") != -1:
  263. data = self._rbuf.split(b"\x00")
  264. for food in data[:-1]:
  265. self._process(food.decode(errors="replace").rstrip("\r\n"))
  266. self._rbuf = data[-1]
  267.  
  268. def _process(self, data):
  269. """
  270. Process a command string.
  271.  
  272. @type data: str
  273. @param data: the command string
  274. """
  275. self._callEvent("onRaw", data)
  276. data = data.split(":")
  277. cmd, args = data[0], data[1:]
  278. func = "_rcmd_" + cmd
  279. if hasattr(self, func):
  280. getattr(self, func)(args)
  281. else:
  282. if debug:
  283. print("unknown data: "+str(data))
  284.  
  285. def _getManager(self): return self._mgr
  286.  
  287. mgr = property(_getManager)
  288.  
  289. ####
  290. # Received Commands
  291. ####
  292.  
  293. def _rcmd_mhs(self, args):
  294. """
  295. note to future maintainers
  296.  
  297. args[1] is ether "online" or "offline"
  298. """
  299. self._connected = True
  300. self._setWriteLock(False)
  301.  
  302. def _rcmd_msg(self, args):
  303. user = User(args[0])
  304. body = _strip_html(":".join(args[5:]))
  305. self._callEvent("onPMMessage", user, body)
  306.  
  307. ####
  308. # Util
  309. ####
  310. def _callEvent(self, evt, *args, **kw):
  311. getattr(self.mgr, evt)(self, *args, **kw)
  312. self.mgr.onEventCalled(self, evt, *args, **kw)
  313.  
  314. def _write(self, data):
  315. if self._wlock:
  316. self._wlockbuf += data
  317. else:
  318. self.mgr._write(self, data)
  319.  
  320. def _setWriteLock(self, lock):
  321. self._wlock = lock
  322. if self._wlock == False:
  323. self._write(self._wlockbuf)
  324. self._wlockbuf = b""
  325.  
  326. def _sendCommand(self, *args):
  327. """
  328. Send a command.
  329.  
  330. @type args: [str, str, ...]
  331. @param args: command and list of arguments
  332. """
  333. if self._firstCommand:
  334. terminator = b"\x00"
  335. self._firstCommand = False
  336. else:
  337. terminator = b"\r\n\x00"
  338. self._write(":".join(args).encode() + terminator)
  339.  
  340. class ANON_PM:
  341. """Comparable wrapper for anon Chatango PM"""
  342. ####
  343. # Init
  344. ####
  345. def __init__(self, mgr):
  346. self._mgr = mgr
  347. self._wlock = False
  348. self._firstCommand = True
  349. self._persons = dict()
  350. self._wlockbuf = b""
  351. self._pingTask = None
  352.  
  353. ####
  354. # Connections
  355. ####
  356. def _connect(self,name):
  357. self._persons[name] = _ANON_PM_OBJECT(self._mgr,name)
  358. sock = socket.socket()
  359. sock.connect((self._mgr._anonPMHost, self._mgr._PMPort))
  360. sock.setblocking(False)
  361. self._persons[name]._sock = sock
  362. if not self._persons[name]._auth(): return
  363. self._persons[name]._pingTask = self._mgr.setInterval(self._mgr._pingDelay, self._persons[name].ping)
  364. self._persons[name]._connected = True
  365.  
  366. def message(self, user, msg):
  367. """send a pm to a user"""
  368. if not user.name in self._persons:
  369. self._connect(user.name)
  370. self._persons[user.name].message(user,msg)
  371.  
  372. def getConnections(self):
  373. return list(self._persons.values())
  374.  
  375. ################################################################
  376. # PM class
  377. ################################################################
  378. class PM:
  379. """Manages a connection with Chatango PM."""
  380. ####
  381. # Init
  382. ####
  383. def __init__(self, mgr):
  384. self._auth_re = re.compile(r"auth\.chatango\.com ?= ?([^;]*)", re.IGNORECASE)
  385. self._connected = False
  386. self._mgr = mgr
  387. self._auid = None
  388. self._blocklist = set()
  389. self._contacts = set()
  390. self._status = dict()
  391. self._wlock = False
  392. self._firstCommand = True
  393. self._wbuf = b""
  394. self._wlockbuf = b""
  395. self._rbuf = b""
  396. self._pingTask = None
  397. self._connect()
  398.  
  399. ####
  400. # Connections
  401. ####
  402. def _connect(self):
  403. self._wbuf = b""
  404. self._sock = socket.socket()
  405. self._sock.connect((self._mgr._PMHost, self._mgr._PMPort))
  406. self._sock.setblocking(False)
  407. self._firstCommand = True
  408. if not self._auth(): return
  409. self._pingTask = self.mgr.setInterval(self._mgr._pingDelay, self.ping)
  410. self._connected = True
  411.  
  412.  
  413. def _getAuth(self, name, password):
  414. """
  415. Request an auid using name and password.
  416.  
  417. @type name: str
  418. @param name: name
  419. @type password: str
  420. @param password: password
  421.  
  422. @rtype: str
  423. @return: auid
  424. """
  425. data = urllib.parse.urlencode({
  426. "user_id": name,
  427. "password": password,
  428. "storecookie": "on",
  429. "checkerrors": "yes"
  430. }).encode()
  431. try:
  432. resp = urllib.request.urlopen("http://chatango.com/login", data)
  433. headers = resp.headers
  434. except Exception:
  435. return None
  436. for header, value in headers.items():
  437. if header.lower() == "set-cookie":
  438. m = self._auth_re.search(value)
  439. if m:
  440. auth = m.group(1)
  441. if auth == "":
  442. return None
  443. return auth
  444. return None
  445.  
  446. def _auth(self):
  447. self._auid = self._getAuth(self._mgr.name, self._mgr.password)
  448. if self._auid == None:
  449. self._sock.close()
  450. self._callEvent("onLoginFail")
  451. self._sock = None
  452. return False
  453. self._sendCommand("tlogin", self._auid, "2")
  454. self._setWriteLock(True)
  455. return True
  456.  
  457. def disconnect(self):
  458. """Disconnect the bot from PM"""
  459. self._disconnect()
  460. self._callEvent("onPMDisconnect")
  461.  
  462. def _disconnect(self):
  463. self._connected = False
  464. self._sock.close()
  465. self._sock = None
  466.  
  467. ####
  468. # Feed
  469. ####
  470. def _feed(self, data):
  471. """
  472. Feed data to the connection.
  473.  
  474. @type data: bytes
  475. @param data: data to be fed
  476. """
  477. self._rbuf += data
  478. while self._rbuf.find(b"\x00") != -1:
  479. data = self._rbuf.split(b"\x00")
  480. for food in data[:-1]:
  481. self._process(food.decode(errors="replace").rstrip("\r\n"))
  482. self._rbuf = data[-1]
  483.  
  484. def _process(self, data):
  485. """
  486. Process a command string.
  487.  
  488. @type data: str
  489. @param data: the command string
  490. """
  491. self._callEvent("onRaw", data)
  492. data = data.split(":")
  493. cmd, args = data[0], data[1:]
  494. func = "_rcmd_" + cmd
  495. if hasattr(self, func):
  496. getattr(self, func)(args)
  497. else:
  498. if debug:
  499. print("unknown data: "+str(data))
  500.  
  501. ####
  502. # Properties
  503. ####
  504. def _getManager(self): return self._mgr
  505. def _getContacts(self): return self._contacts
  506. def _getBlocklist(self): return self._blocklist
  507.  
  508. mgr = property(_getManager)
  509. contacts = property(_getContacts)
  510. blocklist = property(_getBlocklist)
  511.  
  512. ####
  513. # Received Commands
  514. ####
  515. def _rcmd_OK(self, args):
  516. self._setWriteLock(False)
  517. self._sendCommand("wl")
  518. self._sendCommand("getblock")
  519. self._callEvent("onPMConnect")
  520.  
  521. def _rcmd_wl(self, args):
  522. self._contacts = set()
  523. for i in range(len(args) // 4):
  524. name, last_on, is_on, idle = args[i * 4: i * 4 + 4]
  525. user = User(name)
  526. if last_on=="None":pass#in case chatango gives a "None" as data argument
  527. elif not is_on == "on": self._status[user] = [int(last_on), False, 0]
  528. elif idle == '0': self._status[user] = [int(last_on), True, 0]
  529. else: self._status[user] = [int(last_on), True, time.time() - int(idle) * 60]
  530. self._contacts.add(user)
  531. self._callEvent("onPMContactlistReceive")
  532.  
  533. def _rcmd_block_list(self, args):
  534. self._blocklist = set()
  535. for name in args:
  536. if name == "": continue
  537. self._blocklist.add(User(name))
  538.  
  539. def _rcmd_idleupdate(self, args):
  540. user = User(args[0])
  541. last_on, is_on, idle = self._status[user]
  542. if args[1] == '1':
  543. self._status[user] = [last_on, is_on, 0]
  544. else:
  545. self._status[user] = [last_on, is_on, time.time()]
  546.  
  547. def _rcmd_track(self, args):
  548. user = User(args[0])
  549. if user in self._status:
  550. last_on = self._status[user][0]
  551. else:
  552. last_on = 0
  553. if args[1] == '0':
  554. idle = 0
  555. else:
  556. idle = time.time() - int(args[1]) * 60
  557. if args[2] == "online":
  558. is_on = True
  559. else:
  560. is_on = False
  561. self._status[user] = [last_on, is_on, idle]
  562.  
  563. def _rcmd_DENIED(self, args):
  564. self._disconnect()
  565. self._callEvent("onLoginFail")
  566.  
  567. def _rcmd_msg(self, args):
  568. user = User(args[0])
  569. body = _strip_html(":".join(args[5:]))
  570. self._callEvent("onPMMessage", user, body)
  571.  
  572. def _rcmd_msgoff(self, args):
  573. user = User(args[0])
  574. body = _strip_html(":".join(args[5:]))
  575. self._callEvent("onPMOfflineMessage", user, body)
  576.  
  577. def _rcmd_wlonline(self, args):
  578. user = User(args[0])
  579. last_on = float(args[1])
  580. self._status[user] = [last_on,True,last_on]
  581. self._callEvent("onPMContactOnline", user)
  582.  
  583. def _rcmd_wloffline(self, args):
  584. user = User(args[0])
  585. last_on = float(args[1])
  586. self._status[user] = [last_on,False,0]
  587. self._callEvent("onPMContactOffline", user)
  588.  
  589. def _rcmd_kickingoff(self, args):
  590. self.disconnect()
  591.  
  592. def _rcmd_toofast(self, args):
  593. self.disconnect()
  594.  
  595. def _rcmd_unblocked(self, user):
  596. """call when successfully unblocked"""
  597. if user in self._blocklist:
  598. self._blocklist.remove(user)
  599. self._callEvent("onPMUnblock", user)
  600.  
  601.  
  602. ####
  603. # Commands
  604. ####
  605. def ping(self):
  606. """send a ping"""
  607. self._sendCommand("")
  608. self._callEvent("onPMPing")
  609.  
  610. def message(self, user, msg):
  611. """send a pm to a user"""
  612. if msg!=None:
  613. self._sendCommand("msg", user.name, msg)
  614.  
  615. def addContact(self, user):
  616. """add contact"""
  617. if user not in self._contacts:
  618. self._sendCommand("wladd", user.name)
  619. self._contacts.add(user)
  620. self._callEvent("onPMContactAdd", user)
  621.  
  622. def removeContact(self, user):
  623. """remove contact"""
  624. if user in self._contacts:
  625. self._sendCommand("wldelete", user.name)
  626. self._contacts.remove(user)
  627. self._callEvent("onPMContactRemove", user)
  628.  
  629. def block(self, user):
  630. """block a person"""
  631. if user not in self._blocklist:
  632. self._sendCommand("block", user.name, user.name, "S")
  633. self._blocklist.add(user)
  634. self._callEvent("onPMBlock", user)
  635.  
  636. def unblock(self, user):
  637. """unblock a person"""
  638. if user in self._blocklist:
  639. self._sendCommand("unblock", user.name)
  640.  
  641. def track(self, user):
  642. """get and store status of person for future use"""
  643. self._sendCommand("track", user.name)
  644.  
  645. def checkOnline(self, user):
  646. """return True if online, False if offline, None if unknown"""
  647. if user in self._status:
  648. return self._status[user][1]
  649. else:
  650. return None
  651.  
  652. def getIdle(self, user):
  653. """return last active time, time.time() if isn't idle, 0 if offline, None if unknown"""
  654. if not user in self._status: return None
  655. if not self._status[user][1]: return 0
  656. if not self._status[user][2]: return time.time()
  657. else: return self._status[user][2]
  658.  
  659. ####
  660. # Util
  661. ####
  662. def _callEvent(self, evt, *args, **kw):
  663. getattr(self.mgr, evt)(self, *args, **kw)
  664. self.mgr.onEventCalled(self, evt, *args, **kw)
  665.  
  666. def _write(self, data):
  667. if self._wlock:
  668. self._wlockbuf += data
  669. else:
  670. self.mgr._write(self, data)
  671.  
  672. def _setWriteLock(self, lock):
  673. self._wlock = lock
  674. if self._wlock == False:
  675. self._write(self._wlockbuf)
  676. self._wlockbuf = b""
  677.  
  678. def _sendCommand(self, *args):
  679. """
  680. Send a command.
  681.  
  682. @type args: [str, str, ...]
  683. @param args: command and list of arguments
  684. """
  685. if self._firstCommand:
  686. terminator = b"\x00"
  687. self._firstCommand = False
  688. else:
  689. terminator = b"\r\n\x00"
  690. self._write(":".join(args).encode() + terminator)
  691.  
  692. def getConnections(self):
  693. return [self]
  694.  
  695. ################################################################
  696. # Room class
  697. ################################################################
  698. class Room:
  699. """Manages a connection with a Chatango room."""
  700. ####
  701. # Init
  702. ####
  703. def __init__(self, room, uid = None, server = None, port = None, mgr = None):
  704. """init, don't overwrite"""
  705. # Basic stuff
  706. self._name = room
  707. self._server = server or getServer(room)
  708. self._port = port or 443
  709. self._mgr = mgr
  710.  
  711. # Under the hood
  712. self._connected = False
  713. self._reconnecting = False
  714. self._uid = uid or _genUid()
  715. self._rbuf = b""
  716. self._wbuf = b""
  717. self._wlockbuf = b""
  718. self._owner = None
  719. self._mods = set()
  720. self._mqueue = dict()
  721. self._history = list()
  722. self._userlist = list()
  723. self._firstCommand = True
  724. self._connectAmmount = 0
  725. self._premium = False
  726. self._userCount = 0
  727. self._pingTask = None
  728. self._botname = None
  729. self._currentname = None
  730. self._users = dict()
  731. self._msgs = dict()
  732. self._wlock = False
  733. self._silent = False
  734. self._banlist = dict()
  735. self._unbanlist = dict()
  736.  
  737. # Inited vars
  738. if self._mgr: self._connect()
  739.  
  740. ####
  741. # Connect/disconnect
  742. ####
  743. def _connect(self):
  744. """Connect to the server."""
  745. self._sock = socket.socket()
  746. self._sock.connect((self._server, self._port))
  747. self._sock.setblocking(False)
  748. self._firstCommand = True
  749. self._wbuf = b""
  750. self._auth()
  751. self._pingTask = self.mgr.setInterval(self.mgr._pingDelay, self.ping)
  752. if not self._reconnecting: self.connected = True
  753.  
  754. def reconnect(self):
  755. """Reconnect."""
  756. self._reconnect()
  757.  
  758. def _reconnect(self):
  759. """Reconnect."""
  760. self._reconnecting = True
  761. if self.connected:
  762. self._disconnect()
  763. self._uid = _genUid()
  764. self._connect()
  765. self._reconnecting = False
  766.  
  767. def disconnect(self):
  768. """Disconnect."""
  769. self._disconnect()
  770. self._callEvent("onDisconnect")
  771.  
  772. def _disconnect(self):
  773. """Disconnect from the server."""
  774. if not self._reconnecting: self.connected = False
  775. for user in self._userlist:
  776. user.clearSessionIds(self)
  777. self._userlist = list()
  778. self._pingTask.cancel()
  779. self._sock.close()
  780. if not self._reconnecting: del self.mgr._rooms[self.name]
  781.  
  782. def _auth(self):
  783. """Authenticate."""
  784. # login as name with password
  785. if self.mgr.name and self.mgr.password:
  786. self._sendCommand("bauth", self.name, self._uid, self.mgr.name, self.mgr.password)
  787. self._currentname = self.mgr.name
  788. # login as anon
  789. else:
  790. self._sendCommand("bauth", self.name)
  791.  
  792. self._setWriteLock(True)
  793.  
  794. ####
  795. # Properties
  796. ####
  797. def _getName(self): return self._name
  798. def _getBotName(self):
  799. if self.mgr.name and self.mgr.password:
  800. return self.mgr.name
  801. elif self.mgr.name and self.mgr.password == None:
  802. return "#" + self.mgr.name
  803. elif self.mgr.name == None:
  804. return self._botname
  805. def _getCurrentname(self): return self._currentname
  806. def _getManager(self): return self._mgr
  807. def _getUserlist(self, mode = None, unique = None, memory = None):
  808. ul = None
  809. if mode == None: mode = self.mgr._userlistMode
  810. if unique == None: unique = self.mgr._userlistUnique
  811. if memory == None: memory = self.mgr._userlistMemory
  812. if mode == Userlist_Recent:
  813. ul = map(lambda x: x.user, self._history[-memory:])
  814. elif mode == Userlist_All:
  815. ul = self._userlist
  816. if unique:
  817. return list(set(ul))
  818. else:
  819. return ul
  820. def _getUserNames(self):
  821. ul = self.userlist
  822. return list(map(lambda x: x.name, ul))
  823. def _getUser(self): return self.mgr.user
  824. def _getOwner(self): return self._owner
  825. def _getOwnerName(self): return self._owner.name
  826. def _getMods(self):
  827. newset = set()
  828. for mod in self._mods:
  829. newset.add(mod)
  830. return newset
  831. def _getModNames(self):
  832. mods = self._getMods()
  833. return [x.name for x in mods]
  834. def _getUserCount(self): return self._userCount
  835. def _getSilent(self): return self._silent
  836. def _setSilent(self, val): self._silent = val
  837. def _getBanlist(self): return list(self._banlist.keys())
  838. def _getUnBanlist(self): return [[record["target"], record["src"]] for record in self._unbanlist.values()]
  839.  
  840. name = property(_getName)
  841. botname = property(_getBotName)
  842. currentname = property(_getCurrentname)
  843. mgr = property(_getManager)
  844. userlist = property(_getUserlist)
  845. usernames = property(_getUserNames)
  846. user = property(_getUser)
  847. owner = property(_getOwner)
  848. ownername = property(_getOwnerName)
  849. mods = property(_getMods)
  850. modnames = property(_getModNames)
  851. usercount = property(_getUserCount)
  852. silent = property(_getSilent, _setSilent)
  853. banlist = property(_getBanlist)
  854. unbanlist = property(_getUnBanlist)
  855.  
  856. ####
  857. # Feed/process
  858. ####
  859. def _feed(self, data):
  860. """
  861. Feed data to the connection.
  862.  
  863. @type data: bytes
  864. @param data: data to be fed
  865. """
  866. self._rbuf += data
  867. while self._rbuf.find(b"\x00") != -1:
  868. data = self._rbuf.split(b"\x00")
  869. for food in data[:-1]:
  870. self._process(food.decode(errors="replace").rstrip("\r\n"))
  871. self._rbuf = data[-1]
  872.  
  873. def _process(self, data):
  874. """
  875. Process a command string.
  876.  
  877. @type data: str
  878. @param data: the command string
  879. """
  880. self._callEvent("onRaw", data)
  881. data = data.split(":")
  882. cmd, args = data[0], data[1:]
  883. func = "_rcmd_" + cmd
  884. if hasattr(self, func):
  885. getattr(self, func)(args)
  886. else:
  887. if debug:
  888. print("unknown data: "+str(data))
  889.  
  890. ####
  891. # Received Commands
  892. ####
  893. def _rcmd_ok(self, args):
  894. # if no name, join room as anon and no password
  895. if args[2] == "N" and self.mgr.password == None and self.mgr.name == None:
  896. n = args[4].rsplit('.', 1)[0]
  897. n = n[-4:]
  898. aid = args[1][0:8]
  899. pid = "!anon" + _getAnonId(n, aid)
  900. self._botname = pid
  901. self._currentname = pid
  902. self.user._nameColor = n
  903. # if got name, join room as name and no password
  904. elif args[2] == "N" and self.mgr.password == None:
  905. self._sendCommand("blogin", self.mgr.name)
  906. self._currentname = self.mgr.name
  907. # if got password but fail to login
  908. elif args[2] != "M": #unsuccesful login
  909. self._callEvent("onLoginFail")
  910. self.disconnect()
  911. self._owner = User(args[0])
  912. self._uid = args[1]
  913. self._aid = args[1][4:8]
  914. self._mods = set(map(lambda x: User(x.split(",")[0]), args[6].split(";")))
  915. self._i_log = list()
  916.  
  917. def _rcmd_denied(self, args):
  918. self._disconnect()
  919. self._callEvent("onConnectFail")
  920.  
  921. def _rcmd_inited(self, args):
  922. self._sendCommand("g_participants", "start")
  923. self._sendCommand("getpremium", "1")
  924. self.requestBanlist()
  925. self.requestUnBanlist()
  926. if self._connectAmmount == 0:
  927. self._callEvent("onConnect")
  928. for msg in reversed(self._i_log):
  929. user = msg.user
  930. self._callEvent("onHistoryMessage", user, msg)
  931. self._addHistory(msg)
  932. del self._i_log
  933. else:
  934. self._callEvent("onReconnect")
  935. self._connectAmmount += 1
  936. self._setWriteLock(False)
  937.  
  938. def _rcmd_premium(self, args):
  939. if float(args[1]) > time.time():
  940. self._premium = True
  941. if self.user._mbg: self.setBgMode(1)
  942. if self.user._mrec: self.setRecordingMode(1)
  943. else:
  944. self._premium = False
  945.  
  946. def _rcmd_mods(self, args):
  947. modnames = args
  948. mods = set(map(lambda x: User(x.split(",")[0]), modnames))
  949. premods = self._mods
  950. for user in mods - premods: #modded
  951. self._mods.add(user)
  952. self._callEvent("onModAdd", user)
  953. for user in premods - mods: #demodded
  954. self._mods.remove(user)
  955. self._callEvent("onModRemove", user)
  956. self._callEvent("onModChange")
  957.  
  958. def _rcmd_b(self, args):
  959. mtime = float(args[0])
  960. puid = args[3]
  961. ip = args[6]
  962. name = args[1]
  963. rawmsg = ":".join(args[9:])
  964. msg, n, f = _clean_message(rawmsg)
  965. if name == "":
  966. nameColor = None
  967. name = "#" + args[2]
  968. if name == "#":
  969. name = "!anon" + _getAnonId(n, puid)
  970. else:
  971. if n: nameColor = _parseNameColor(n)
  972. else: nameColor = None
  973. i = args[5]
  974. unid = args[4]
  975. user = User(name)
  976. #Create an anonymous message and queue it because msgid is unknown.
  977. if f: fontColor, fontFace, fontSize = _parseFont(f)
  978. else: fontColor, fontFace, fontSize = None, None, None
  979. msg = Message(
  980. time = mtime,
  981. user = user,
  982. body = msg,
  983. raw = rawmsg,
  984. ip = ip,
  985. nameColor = nameColor,
  986. fontColor = fontColor,
  987. fontFace = fontFace,
  988. fontSize = fontSize,
  989. unid = unid,
  990. puid = puid,
  991. room = self
  992. )
  993. self._mqueue[i] = msg
  994.  
  995. def _rcmd_u(self, args):
  996. temp = Struct(**self._mqueue)
  997. if hasattr(temp, args[0]):
  998. msg = getattr(temp, args[0])
  999. if msg.user != self.user:
  1000. msg.user._fontColor = msg.fontColor
  1001. msg.user._fontFace = msg.fontFace
  1002. msg.user._fontSize = msg.fontSize
  1003. msg.user._nameColor = msg.nameColor
  1004. del self._mqueue[args[0]]
  1005. msg.attach(self, args[1])
  1006. self._addHistory(msg)
  1007. self._callEvent("onMessage", msg.user, msg)
  1008.  
  1009. def _rcmd_i(self, args):
  1010. mtime = float(args[0])
  1011. puid = args[3]
  1012. ip = args[6]
  1013. name = args[1]
  1014. rawmsg = ":".join(args[9:])
  1015. msg, n, f = _clean_message(rawmsg)
  1016. if name == "":
  1017. nameColor = None
  1018. name = "#" + args[2]
  1019. if name == "#":
  1020. name = "!anon" + _getAnonId(n, puid)
  1021. else:
  1022. if n: nameColor = _parseNameColor(n)
  1023. else: nameColor = None
  1024. i = args[5]
  1025. unid = args[4]
  1026. user = User(name)
  1027. #Create an anonymous message and queue it because msgid is unknown.
  1028. if f: fontColor, fontFace, fontSize = _parseFont(f)
  1029. else: fontColor, fontFace, fontSize = None, None, None
  1030. msg = Message(
  1031. time = mtime,
  1032. user = user,
  1033. body = msg,
  1034. raw = rawmsg,
  1035. ip = ip,
  1036. nameColor = nameColor,
  1037. fontColor = fontColor,
  1038. fontFace = fontFace,
  1039. fontSize = fontSize,
  1040. unid = unid,
  1041. puid = puid,
  1042. room = self
  1043. )
  1044. self._i_log.append(msg)
  1045.  
  1046. def _rcmd_g_participants(self, args):
  1047. args = ":".join(args)
  1048. args = args.split(";")
  1049. for data in args:
  1050. data = data.split(":")
  1051. name = data[3].lower()
  1052. if name == "none": continue
  1053. user = User(
  1054. name = name,
  1055. room = self
  1056. )
  1057. user.addSessionId(self, data[0])
  1058. self._userlist.append(user)
  1059.  
  1060. def _rcmd_participant(self, args):
  1061. name = args[3].lower()
  1062. if name == "none": return
  1063. user = User(name)
  1064. puid = args[2]
  1065.  
  1066. if args[0] == "0": #leave
  1067. user.removeSessionId(self, args[1])
  1068. self._userlist.remove(user)
  1069. if user not in self._userlist or not self.mgr._userlistEventUnique:
  1070. self._callEvent("onLeave", user, puid)
  1071. else: #join
  1072. user.addSessionId(self, args[1])
  1073. if user not in self._userlist: doEvent = True
  1074. else: doEvent = False
  1075. self._userlist.append(user)
  1076. if doEvent or not self.mgr._userlistEventUnique:
  1077. self._callEvent("onJoin", user, puid)
  1078.  
  1079. def _rcmd_show_fw(self, args):
  1080. self._callEvent("onFloodWarning")
  1081.  
  1082. def _rcmd_show_tb(self, args):
  1083. self._callEvent("onFloodBan")
  1084.  
  1085. def _rcmd_tb(self, args):
  1086. self._callEvent("onFloodBanRepeat")
  1087.  
  1088. def _rcmd_delete(self, args):
  1089. msg = self._msgs.get(args[0])
  1090. if msg:
  1091. if msg in self._history:
  1092. self._history.remove(msg)
  1093. self._callEvent("onMessageDelete", msg.user, msg)
  1094. msg.detach()
  1095.  
  1096. def _rcmd_deleteall(self, args):
  1097. for msgid in args:
  1098. self._rcmd_delete([msgid])
  1099.  
  1100. def _rcmd_n(self, args):
  1101. self._userCount = int(args[0], 16)
  1102. self._callEvent("onUserCountChange")
  1103.  
  1104. def _rcmd_blocklist(self, args):
  1105. self._banlist = dict()
  1106. sections = ":".join(args).split(";")
  1107. for section in sections:
  1108. params = section.split(":")
  1109. if len(params) != 5: continue
  1110. if params[2] == "": continue
  1111. user = User(params[2])
  1112. self._banlist[user] = {
  1113. "unid":params[0],
  1114. "ip":params[1],
  1115. "target":user,
  1116. "time":float(params[3]),
  1117. "src":User(params[4])
  1118. }
  1119. self._callEvent("onBanlistUpdate")
  1120.  
  1121. def _rcmd_unblocklist(self, args):
  1122. self._unbanlist = dict()
  1123. sections = ":".join(args).split(";")
  1124. for section in sections:
  1125. params = section.split(":")
  1126. if len(params) != 5: continue
  1127. if params[2] == "": continue
  1128. user = User(params[2])
  1129. self._unbanlist[user] = {
  1130. "unid":params[0],
  1131. "ip":params[1],
  1132. "target":user,
  1133. "time":float(params[3]),
  1134. "src":User(params[4])
  1135. }
  1136. self._callEvent("onUnBanlistUpdate")
  1137.  
  1138. def _rcmd_blocked(self, args):
  1139. if args[2] == "": return
  1140. target = User(args[2])
  1141. user = User(args[3])
  1142. self._banlist[target] = {"unid":args[0], "ip":args[1], "target":target, "time":float(args[4]), "src":user}
  1143. self._callEvent("onBan", user, target)
  1144.  
  1145. def _rcmd_unblocked(self, args):
  1146. if args[2] == "": return
  1147. target = User(args[2])
  1148. user=User(args[3])
  1149. del self._banlist[target]
  1150. self._unbanlist[user] = {"unid":args[0], "ip":args[1], "target":target, "time":float(args[4]), "src":user}
  1151. self._callEvent("onUnban", user, target)
  1152.  
  1153. ####
  1154. # Commands
  1155. ####
  1156. def login(self, NAME, PASS = None):
  1157. """login as a user or set a name in room"""
  1158. if PASS:
  1159. self._sendCommand("blogin", NAME, PASS)
  1160. else:
  1161. self._sendCommand("blogin", NAME)
  1162. self._currentname = NAME
  1163.  
  1164. def logout(self):
  1165. """logout of user in a room"""
  1166. self._sendCommand("blogout")
  1167. self._currentname = self._botname
  1168.  
  1169. def ping(self):
  1170. """Send a ping."""
  1171. self._sendCommand("")
  1172. self._callEvent("onPing")
  1173.  
  1174. def rawMessage(self, msg):
  1175. """
  1176. Send a message without n and f tags.
  1177.  
  1178. @type msg: str
  1179. @param msg: message
  1180. """
  1181. if not self._silent:
  1182. self._sendCommand("bmsg:tl2r", msg)
  1183.  
  1184. def message(self, msg, html = False):
  1185. """
  1186. Send a message. (Use "\n" for new line)
  1187.  
  1188. @type msg: str
  1189. @param msg: message
  1190. """
  1191. if msg==None:
  1192. return
  1193. msg = msg.rstrip()
  1194. if not html:
  1195. msg = msg.replace("<", "&lt;").replace(">", "&gt;")
  1196. if len(msg) > self.mgr._maxLength:
  1197. if self.mgr._tooBigMessage == BigMessage_Cut:
  1198. self.message(msg[:self.mgr._maxLength], html = html)
  1199. elif self.mgr._tooBigMessage == BigMessage_Multiple:
  1200. while len(msg) > 0:
  1201. sect = msg[:self.mgr._maxLength]
  1202. msg = msg[self.mgr._maxLength:]
  1203. self.message(sect, html = html)
  1204. return
  1205. msg = "<n" + self.user.nameColor + "/>" + msg
  1206. if self._currentname != None and not self._currentname.startswith("!anon"):
  1207. font_properties = "<f x%0.2i%s=\"%s\">" %(self.user.fontSize, self.user.fontColor, self.user.fontFace)
  1208. if "\n" in msg:
  1209. msg.replace("\n", "</f></p><p>%s" %(font_properties))
  1210. msg = font_properties + msg
  1211. msg.replace("~","&#126;")
  1212. self.rawMessage(msg)
  1213.  
  1214. def setBgMode(self, mode):
  1215. """turn on/off bg"""
  1216. self._sendCommand("msgbg", str(mode))
  1217.  
  1218. def setRecordingMode(self, mode):
  1219. """turn on/off rcecording"""
  1220. self._sendCommand("msgmedia", str(mode))
  1221.  
  1222. def addMod(self, user):
  1223. """
  1224. Add a moderator.
  1225.  
  1226. @type user: User
  1227. @param user: User to mod.
  1228. """
  1229. if self.getLevel(User(self.currentname)) == 2:
  1230. self._sendCommand("addmod", user.name)
  1231.  
  1232. def removeMod(self, user):
  1233. """
  1234. Remove a moderator.
  1235.  
  1236. @type user: User
  1237. @param user: User to demod.
  1238. """
  1239. if self.getLevel(User(self.currentname)) == 2:
  1240. self._sendCommand("removemod", user.name)
  1241.  
  1242. def flag(self, message):
  1243. """
  1244. Flag a message.
  1245.  
  1246. @type message: Message
  1247. @param message: message to flag
  1248. """
  1249. self._sendCommand("g_flag", message.msgid)
  1250.  
  1251. def flagUser(self, user):
  1252. """
  1253. Flag a user.
  1254.  
  1255. @type user: User
  1256. @param user: user to flag
  1257.  
  1258. @rtype: bool
  1259. @return: whether a message to flag was found
  1260. """
  1261. msg = self.getLastMessage(user)
  1262. if msg:
  1263. self.flag(msg)
  1264. return True
  1265. return False
  1266.  
  1267. def deleteMessage(self, message):
  1268. """
  1269. Delete a message. (Moderator only)
  1270.  
  1271. @type message: Message
  1272. @param message: message to delete
  1273. """
  1274. if self.getLevel(self.user) > 0:
  1275. self._sendCommand("delmsg", message.msgid)
  1276.  
  1277. def deleteUser(self, user):
  1278. """
  1279. Delete a message. (Moderator only)
  1280.  
  1281. @type message: User
  1282. @param message: delete user's last message
  1283. """
  1284. if self.getLevel(self.user) > 0:
  1285. msg = self.getLastMessage(user)
  1286. if msg:
  1287. self._sendCommand("delmsg", msg.msgid)
  1288. return True
  1289. return False
  1290.  
  1291. def delete(self, message):
  1292. """
  1293. compatibility wrapper for deleteMessage
  1294. """
  1295. print("[obsolete] the delete function is obsolete, please use deleteMessage")
  1296. return self.deleteMessage(message)
  1297.  
  1298. def rawClearUser(self, unid, ip, user):
  1299. self._sendCommand("delallmsg", unid, ip, user)
  1300. def clearUser(self, user):
  1301. """
  1302. Clear all of a user's messages. (Moderator only)
  1303.  
  1304. @type user: User
  1305. @param user: user to delete messages of
  1306.  
  1307. @rtype: bool
  1308. @return: whether a message to delete was found
  1309. """
  1310. if self.getLevel(self.user) > 0:
  1311. msg = self.getLastMessage(user)
  1312. if msg:
  1313. if msg.user.name[0] in ["!","#"]:self.rawClearUser(msg.unid, msg.ip,"")
  1314. else:self.rawClearUser(msg.unid,msg.ip,msg.user.name)
  1315. return True
  1316. return False
  1317.  
  1318. def clearall(self):
  1319. """Clear all messages. (Owner only)"""
  1320. if self.getLevel(self.user) == 2:
  1321. self._sendCommand("clearall")
  1322.  
  1323. def rawBan(self, name, ip, unid):
  1324. """
  1325. Execute the block command using specified arguments.
  1326. (For advanced usage)
  1327.  
  1328. @type name: str
  1329. @param name: name
  1330. @type ip: str
  1331. @param ip: ip address
  1332. @type unid: str
  1333. @param unid: unid
  1334. """
  1335. self._sendCommand("block", unid, ip, name)
  1336.  
  1337. def ban(self, msg):
  1338. """
  1339. Ban a message's sender. (Moderator only)
  1340.  
  1341. @type message: Message
  1342. @param message: message to ban sender of
  1343. """
  1344. if self.getLevel(self.user) > 0:
  1345. self.rawBan(msg.user.name, msg.ip, msg.unid)
  1346.  
  1347. def banUser(self, user):
  1348. """
  1349. Ban a user. (Moderator only)
  1350.  
  1351. @type user: User
  1352. @param user: user to ban
  1353.  
  1354. @rtype: bool
  1355. @return: whether a message to ban the user was found
  1356. """
  1357. msg = self.getLastMessage(user)
  1358. if msg:
  1359. self.ban(msg)
  1360. return True
  1361. return False
  1362.  
  1363. def requestBanlist(self):
  1364. """Request an updated banlist."""
  1365. self._sendCommand("blocklist", "block", "", "next", "500")
  1366.  
  1367. def requestUnBanlist(self):
  1368. """Request an updated banlist."""
  1369. self._sendCommand("blocklist", "unblock", "", "next", "500")
  1370.  
  1371. def rawUnban(self, name, ip, unid):
  1372. """
  1373. Execute the unblock command using specified arguments.
  1374. (For advanced usage)
  1375.  
  1376. @type name: str
  1377. @param name: name
  1378. @type ip: str
  1379. @param ip: ip address
  1380. @type unid: str
  1381. @param unid: unid
  1382. """
  1383. self._sendCommand("removeblock", unid, ip, name)
  1384.  
  1385. def unban(self, user):
  1386. """
  1387. Unban a user. (Moderator only)
  1388.  
  1389. @type user: User
  1390. @param user: user to unban
  1391.  
  1392. @rtype: bool
  1393. @return: whether it succeeded
  1394. """
  1395. rec = self._getBanRecord(user)
  1396. if rec:
  1397. self.rawUnban(rec["target"].name, rec["ip"], rec["unid"])
  1398. return True
  1399. else:
  1400. return False
  1401.  
  1402. ####
  1403. # Util
  1404. ####
  1405. def _getBanRecord(self, user):
  1406. if user in self._banlist:
  1407. return self._banlist[user]
  1408. return None
  1409.  
  1410. def _callEvent(self, evt, *args, **kw):
  1411. getattr(self.mgr, evt)(self, *args, **kw)
  1412. self.mgr.onEventCalled(self, evt, *args, **kw)
  1413.  
  1414. def _write(self, data):
  1415. if self._wlock:
  1416. self._wlockbuf += data
  1417. else:
  1418. self.mgr._write(self, data)
  1419.  
  1420. def _setWriteLock(self, lock):
  1421. self._wlock = lock
  1422. if self._wlock == False:
  1423. self._write(self._wlockbuf)
  1424. self._wlockbuf = b""
  1425.  
  1426. def _sendCommand(self, *args):
  1427. """
  1428. Send a command.
  1429.  
  1430. @type args: [str, str, ...]
  1431. @param args: command and list of arguments
  1432. """
  1433. if self._firstCommand:
  1434. terminator = b"\x00"
  1435. self._firstCommand = False
  1436. else:
  1437. terminator = b"\r\n\x00"
  1438. self._write(":".join(args).encode() + terminator)
  1439.  
  1440. def getLevel(self, user):
  1441. """get the level of user in a room"""
  1442. if user == self._owner: return 2
  1443. if user.name in self.modnames: return 1
  1444. return 0
  1445.  
  1446. def getLastMessage(self, user = None):
  1447. """get last message said by user in a room"""
  1448. if user:
  1449. try:
  1450. i = 1
  1451. while True:
  1452. msg = self._history[-i]
  1453. if msg.user == user:
  1454. return msg
  1455. i += 1
  1456. except IndexError:
  1457. return None
  1458. else:
  1459. try:
  1460. return self._history[-1]
  1461. except IndexError:
  1462. return None
  1463. return None
  1464.  
  1465. def findUser(self, name):
  1466. """check if user is in the room
  1467.  
  1468. return User(name) if name in room else None"""
  1469. name = name.lower()
  1470. ul = self._getUserlist()
  1471. udi = dict(zip([u.name for u in ul], ul))
  1472. cname = None
  1473. for n in udi.keys():
  1474. if name in n:
  1475. if cname: return None #ambiguous!!
  1476. cname = n
  1477. if cname: return udi[cname]
  1478. else: return None
  1479.  
  1480. ####
  1481. # History
  1482. ####
  1483. def _addHistory(self, msg):
  1484. """
  1485. Add a message to history.
  1486.  
  1487. @type msg: Message
  1488. @param msg: message
  1489. """
  1490. self._history.append(msg)
  1491. if len(self._history) > self.mgr._maxHistoryLength:
  1492. rest, self._history = self._history[:-self.mgr._maxHistoryLength], self._history[-self.mgr._maxHistoryLength:]
  1493. for msg in rest: msg.detach()
  1494.  
  1495. ################################################################
  1496. # RoomManager class
  1497. ################################################################
  1498. class RoomManager:
  1499. """Class that manages multiple connections."""
  1500. ####
  1501. # Config
  1502. ####
  1503. _Room = Room
  1504. _PM = PM
  1505. _ANON_PM = ANON_PM
  1506. _anonPMHost = "b1.chatango.com"
  1507. _PMHost = "c1.chatango.com"
  1508. _PMPort = 5222
  1509. _TimerResolution = 0.2 #at least x times per second
  1510. _pingDelay = 20
  1511. _userlistMode = Userlist_Recent
  1512. _userlistUnique = True
  1513. _userlistMemory = 50
  1514. _userlistEventUnique = False
  1515. _tooBigMessage = BigMessage_Multiple
  1516. _maxLength = 1800
  1517. _maxHistoryLength = 150
  1518.  
  1519. ####
  1520. # Init
  1521. ####
  1522. def __init__(self, name = None, password = None, pm = True):
  1523. self._name = name
  1524. self._password = password
  1525. self._running = False
  1526. self._tasks = set()
  1527. self._rooms = dict()
  1528. self._rooms_queue = queue.Queue()
  1529. self._rooms_lock = threading.Lock()
  1530. if pm:
  1531. if self._password:
  1532. self._pm = self._PM(mgr = self)
  1533. else:
  1534. self._pm = self._ANON_PM(mgr = self)
  1535. else:
  1536. self._pm = None
  1537.  
  1538. def _joinThread(self):
  1539. while True:
  1540. room = self._rooms_queue.get()
  1541. with self._rooms_lock:
  1542. con = self._Room(room, mgr = self)
  1543. self._rooms[room] = con
  1544.  
  1545. ####
  1546. # Join/leave
  1547. ####
  1548. def joinRoom(self, room):
  1549. """
  1550. Join a room or return None if already joined.
  1551.  
  1552. @type room: str
  1553. @param room: room to join
  1554.  
  1555. @rtype: Room or None
  1556. @return: True or nothing
  1557. """
  1558. room = room.lower()
  1559. if room not in self._rooms:
  1560. self._rooms_queue.put(room)
  1561. return True
  1562. else:
  1563. return None
  1564.  
  1565. def leaveRoom(self, room):
  1566. """
  1567. Leave a room.
  1568.  
  1569. @type room: str
  1570. @param room: room to leave
  1571. """
  1572. room = room.lower()
  1573. if room in self._rooms:
  1574. with self._rooms_lock:
  1575. con = self._rooms[room]
  1576. con.disconnect()
  1577.  
  1578. def getRoom(self, room):
  1579. """
  1580. Get room with a name, or None if not connected to this room.
  1581.  
  1582. @type room: str
  1583. @param room: room
  1584.  
  1585. @rtype: Room
  1586. @return: the room
  1587. """
  1588. room = room.lower()
  1589. if room in self._rooms:
  1590. return self._rooms[room]
  1591. else:
  1592. return None
  1593.  
  1594. ####
  1595. # Properties
  1596. ####
  1597. def _getUser(self): return User(self._name)
  1598. def _getName(self): return self._name
  1599. def _getPassword(self): return self._password
  1600. def _getRooms(self): return set(self._rooms.values())
  1601. def _getRoomNames(self): return set(self._rooms.keys())
  1602. def _getPM(self): return self._pm
  1603.  
  1604. user = property(_getUser)
  1605. name = property(_getName)
  1606. password = property(_getPassword)
  1607. rooms = property(_getRooms)
  1608. roomnames = property(_getRoomNames)
  1609. pm = property(_getPM)
  1610.  
  1611. ####
  1612. # Virtual methods
  1613. ####
  1614. def onInit(self):
  1615. """Called on init."""
  1616. pass
  1617.  
  1618. def safePrint(self, text):
  1619. """Use this to safely print text with unicode"""
  1620. while True:
  1621. try:
  1622. print(text)
  1623. break
  1624. except UnicodeEncodeError as ex:
  1625. text = (text[0:ex.start]+'(unicode)'+text[ex.end:])
  1626.  
  1627. def onConnect(self, room):
  1628. """
  1629. Called when connected to the room.
  1630.  
  1631. @type room: Room
  1632. @param room: room where the event occured
  1633. """
  1634. pass
  1635.  
  1636. def onReconnect(self, room):
  1637. """
  1638. Called when reconnected to the room.
  1639.  
  1640. @type room: Room
  1641. @param room: room where the event occured
  1642. """
  1643. pass
  1644.  
  1645. def onConnectFail(self, room):
  1646. """
  1647. Called when the connection failed.
  1648.  
  1649. @type room: Room
  1650. @param room: room where the event occured
  1651. """
  1652. pass
  1653.  
  1654. def onDisconnect(self, room):
  1655. """
  1656. Called when the client gets disconnected.
  1657.  
  1658. @type room: Room
  1659. @param room: room where the event occured
  1660. """
  1661. pass
  1662.  
  1663. def onLoginFail(self, room):
  1664. """
  1665. Called on login failure, disconnects after.
  1666.  
  1667. @type room: Room
  1668. @param room: room where the event occured
  1669. """
  1670. pass
  1671.  
  1672. def onFloodBan(self, room):
  1673. """
  1674. Called when either flood banned or flagged.
  1675.  
  1676. @type room: Room
  1677. @param room: room where the event occured
  1678. """
  1679. pass
  1680.  
  1681. def onFloodBanRepeat(self, room):
  1682. """
  1683. Called when trying to send something when floodbanned.
  1684.  
  1685. @type room: Room
  1686. @param room: room where the event occured
  1687. """
  1688. pass
  1689.  
  1690. def onFloodWarning(self, room):
  1691. """
  1692. Called when an overflow warning gets received.
  1693.  
  1694. @type room: Room
  1695. @param room: room where the event occured
  1696. """
  1697. pass
  1698.  
  1699. def onMessageDelete(self, room, user, message):
  1700. """
  1701. Called when a message gets deleted.
  1702.  
  1703. @type room: Room
  1704. @param room: room where the event occured
  1705. @type user: User
  1706. @param user: owner of deleted message
  1707. @type message: Message
  1708. @param message: message that got deleted
  1709. """
  1710. pass
  1711.  
  1712. def onModChange(self, room):
  1713. """
  1714. Called when the moderator list changes.
  1715.  
  1716. @type room: Room
  1717. @param room: room where the event occured
  1718. """
  1719. pass
  1720.  
  1721. def onModAdd(self, room, user):
  1722. """
  1723. Called when a moderator gets added.
  1724.  
  1725. @type room: Room
  1726. @param room: room where the event occured
  1727. """
  1728. pass
  1729.  
  1730. def onModRemove(self, room, user):
  1731. """
  1732. Called when a moderator gets removed.
  1733.  
  1734. @type room: Room
  1735. @param room: room where the event occured
  1736. """
  1737. pass
  1738.  
  1739. def onMessage(self, room, user, message):
  1740. """
  1741. Called when a message gets received.
  1742.  
  1743. @type room: Room
  1744. @param room: room where the event occured
  1745. @type user: User
  1746. @param user: owner of message
  1747. @type message: Message
  1748. @param message: received message
  1749. """
  1750. pass
  1751.  
  1752. def onHistoryMessage(self, room, user, message):
  1753. """
  1754. Called when a message gets received from history.
  1755.  
  1756. @type room: Room
  1757. @param room: room where the event occured
  1758. @type user: User
  1759. @param user: owner of message
  1760. @type message: Message
  1761. @param message: the message that got added
  1762. """
  1763. pass
  1764.  
  1765. def onJoin(self, room, user, puid):
  1766. """
  1767. Called when a user joins. Anonymous users get ignored here.
  1768.  
  1769. @type room: Room
  1770. @param room: room where the event occured
  1771. @type user: User
  1772. @param user: the user that has joined
  1773. @type puid: str
  1774. @param puid: the personal unique id for the user
  1775. """
  1776. pass
  1777.  
  1778. def onLeave(self, room, user, puid):
  1779. """
  1780. Called when a user leaves. Anonymous users get ignored here.
  1781.  
  1782. @type room: Room
  1783. @param room: room where the event occured
  1784. @type user: User
  1785. @param user: the user that has left
  1786. @type puid: str
  1787. @param puid: the personal unique id for the user
  1788. """
  1789. pass
  1790.  
  1791. def onRaw(self, room, raw):
  1792. """
  1793. Called before any command parsing occurs.
  1794.  
  1795. @type room: Room
  1796. @param room: room where the event occured
  1797. @type raw: str
  1798. @param raw: raw command data
  1799. """
  1800. pass
  1801.  
  1802. def onPing(self, room):
  1803. """
  1804. Called when a ping gets sent.
  1805.  
  1806. @type room: Room
  1807. @param room: room where the event occured
  1808. """
  1809. pass
  1810.  
  1811. def onUserCountChange(self, room):
  1812. """
  1813. Called when the user count changes.
  1814.  
  1815. @type room: Room
  1816. @param room: room where the event occured
  1817. """
  1818. pass
  1819.  
  1820. def onBan(self, room, user, target):
  1821. """
  1822. Called when a user gets banned.
  1823.  
  1824. @type room: Room
  1825. @param room: room where the event occured
  1826. @type user: User
  1827. @param user: user that banned someone
  1828. @type target: User
  1829. @param target: user that got banned
  1830. """
  1831. pass
  1832.  
  1833. def onUnban(self, room, user, target):
  1834. """
  1835. Called when a user gets unbanned.
  1836.  
  1837. @type room: Room
  1838. @param room: room where the event occured
  1839. @type user: User
  1840. @param user: user that unbanned someone
  1841. @type target: User
  1842. @param target: user that got unbanned
  1843. """
  1844. pass
  1845.  
  1846. def onBanlistUpdate(self, room):
  1847. """
  1848. Called when a banlist gets updated.
  1849.  
  1850. @type room: Room
  1851. @param room: room where the event occured
  1852. """
  1853. pass
  1854.  
  1855. def onUnBanlistUpdate(self, room):
  1856. """
  1857. Called when a unbanlist gets updated.
  1858.  
  1859. @type room: Room
  1860. @param room: room where the event occured
  1861. """
  1862. pass
  1863.  
  1864. def onPMConnect(self, pm):
  1865. """
  1866. Called when connected to the pm
  1867.  
  1868. @type pm: PM
  1869. @param pm: the pm
  1870. """
  1871. pass
  1872.  
  1873. def onAnonPMDisconnect(self, pm, user):
  1874. """
  1875. Called when disconnected from the pm
  1876.  
  1877. @type pm: PM
  1878. @param pm: the pm
  1879. """
  1880. pass
  1881.  
  1882. def onPMDisconnect(self, pm):
  1883. """
  1884. Called when disconnected from the pm
  1885.  
  1886. @type pm: PM
  1887. @param pm: the pm
  1888. """
  1889. pass
  1890.  
  1891. def onPMPing(self, pm):
  1892. """
  1893. Called when sending a ping to the pm
  1894.  
  1895. @type pm: PM
  1896. @param pm: the pm
  1897. """
  1898. pass
  1899.  
  1900. def onPMMessage(self, pm, user, body):
  1901. """
  1902. Called when a message is received
  1903.  
  1904. @type pm: PM
  1905. @param pm: the pm
  1906. @type user: User
  1907. @param user: owner of message
  1908. @type message: Message
  1909. @param message: received message
  1910. """
  1911. pass
  1912.  
  1913. def onPMOfflineMessage(self, pm, user, body):
  1914. """
  1915. Called when connected if a message is received while offline
  1916.  
  1917. @type pm: PM
  1918. @param pm: the pm
  1919. @type user: User
  1920. @param user: owner of message
  1921. @type message: Message
  1922. @param message: received message
  1923. """
  1924. pass
  1925.  
  1926. def onPMContactlistReceive(self, pm):
  1927. """
  1928. Called when the contact list is received
  1929.  
  1930. @type pm: PM
  1931. @param pm: the pm
  1932. """
  1933. pass
  1934.  
  1935. def onPMBlocklistReceive(self, pm):
  1936. """
  1937. Called when the block list is received
  1938.  
  1939. @type pm: PM
  1940. @param pm: the pm
  1941. """
  1942. pass
  1943.  
  1944. def onPMContactAdd(self, pm, user):
  1945. """
  1946. Called when the contact added message is received
  1947.  
  1948. @type pm: PM
  1949. @param pm: the pm
  1950. @type user: User
  1951. @param user: the user that gotten added
  1952. """
  1953. pass
  1954.  
  1955. def onPMContactRemove(self, pm, user):
  1956. """
  1957. Called when the contact remove message is received
  1958.  
  1959. @type pm: PM
  1960. @param pm: the pm
  1961. @type user: User
  1962. @param user: the user that gotten remove
  1963. """
  1964. pass
  1965.  
  1966. def onPMBlock(self, pm, user):
  1967. """
  1968. Called when successfully block a user
  1969.  
  1970. @type pm: PM
  1971. @param pm: the pm
  1972. @type user: User
  1973. @param user: the user that gotten block
  1974. """
  1975. pass
  1976.  
  1977. def onPMUnblock(self, pm, user):
  1978. """
  1979. Called when successfully unblock a user
  1980.  
  1981. @type pm: PM
  1982. @param pm: the pm
  1983. @type user: User
  1984. @param user: the user that gotten unblock
  1985. """
  1986. pass
  1987.  
  1988. def onPMContactOnline(self, pm, user):
  1989. """
  1990. Called when a user from the contact come online
  1991.  
  1992. @type pm: PM
  1993. @param pm: the pm
  1994. @type user: User
  1995. @param user: the user that came online
  1996. """
  1997. pass
  1998.  
  1999. def onPMContactOffline(self, pm, user):
  2000. """
  2001. Called when a user from the contact go offline
  2002.  
  2003. @type pm: PM
  2004. @param pm: the pm
  2005. @type user: User
  2006. @param user: the user that went offline
  2007. """
  2008. pass
  2009.  
  2010. def onEventCalled(self, room, evt, *args, **kw):
  2011. """
  2012. Called on every room-based event.
  2013.  
  2014. @type room: Room
  2015. @param room: room where the event occured
  2016. @type evt: str
  2017. @param evt: the event
  2018. """
  2019. pass
  2020.  
  2021. ####
  2022. # Deferring
  2023. ####
  2024. def deferToThread(self, callback, func, *args, **kw):
  2025. """
  2026. Defer a function to a thread and callback the return value.
  2027.  
  2028. @type callback: function
  2029. @param callback: function to call on completion
  2030. @type cbargs: tuple or list
  2031. @param cbargs: arguments to get supplied to the callback
  2032. @type func: function
  2033. @param func: function to call
  2034. """
  2035. def f(func, callback, *args, **kw):
  2036. ret = func(*args, **kw)
  2037. self.setTimeout(0, callback, ret)
  2038. threading._start_new_thread(f, (func, callback) + args, kw)
  2039.  
  2040. ####
  2041. # Scheduling
  2042. ####
  2043. class _Task:
  2044. def cancel(self):
  2045. """Sugar for removeTask."""
  2046. self.mgr.removeTask(self)
  2047.  
  2048. def _tick(self):
  2049. now = time.time()
  2050. for task in set(self._tasks):
  2051. if task.target <= now:
  2052. task.func(*task.args, **task.kw)
  2053. if task.isInterval:
  2054. task.target = now + task.timeout
  2055. else:
  2056. self._tasks.remove(task)
  2057.  
  2058. def setTimeout(self, timeout, func, *args, **kw):
  2059. """
  2060. Call a function after at least timeout seconds with specified arguments.
  2061.  
  2062. @type timeout: int
  2063. @param timeout: timeout
  2064. @type func: function
  2065. @param func: function to call
  2066.  
  2067. @rtype: _Task
  2068. @return: object representing the task
  2069. """
  2070. task = self._Task()
  2071. task.mgr = self
  2072. task.target = time.time() + timeout
  2073. task.timeout = timeout
  2074. task.func = func
  2075. task.isInterval = False
  2076. task.args = args
  2077. task.kw = kw
  2078. self._tasks.add(task)
  2079. return task
  2080.  
  2081. def setInterval(self, timeout, func, *args, **kw):
  2082. """
  2083. Call a function at least every timeout seconds with specified arguments.
  2084.  
  2085. @type timeout: int
  2086. @param timeout: timeout
  2087. @type func: function
  2088. @param func: function to call
  2089.  
  2090. @rtype: _Task
  2091. @return: object representing the task
  2092. """
  2093. task = self._Task()
  2094. task.mgr = self
  2095. task.target = time.time() + timeout
  2096. task.timeout = timeout
  2097. task.func = func
  2098. task.isInterval = True
  2099. task.args = args
  2100. task.kw = kw
  2101. self._tasks.add(task)
  2102. return task
  2103.  
  2104. def removeTask(self, task):
  2105. """
  2106. Cancel a task.
  2107.  
  2108. @type task: _Task
  2109. @param task: task to cancel
  2110. """
  2111. self._tasks.remove(task)
  2112.  
  2113. ####
  2114. # Util
  2115. ####
  2116. def _write(self, room, data):
  2117. room._wbuf += data
  2118.  
  2119. def getConnections(self):
  2120. li = list(self._rooms.values())
  2121. if self._pm:
  2122. li.extend(self._pm.getConnections())
  2123. return [c for c in li if c._sock != None]
  2124.  
  2125. ####
  2126. # Main
  2127. ####
  2128. def main(self):
  2129. self.onInit()
  2130. self._running = True
  2131. for l in range(0,Number_of_Threads):
  2132. t = threading.Thread(target=self._joinThread)
  2133. t.daemon = True
  2134. t.start()
  2135. while self._running:
  2136. conns = self.getConnections()
  2137. socks = [x._sock for x in conns]
  2138. wsocks = [x._sock for x in conns if x._wbuf != b""]
  2139. rd, wr, sp = select.select(socks, wsocks, [], self._TimerResolution)
  2140. for sock in rd:
  2141. con = [c for c in conns if c._sock == sock][0]
  2142. try:
  2143. data = sock.recv(1024)
  2144. if(len(data) > 0):
  2145. con._feed(data)
  2146. else:
  2147. con.disconnect()
  2148. except socket.error:
  2149. pass
  2150. for sock in wr:
  2151. con = [c for c in conns if c._sock == sock][0]
  2152. try:
  2153. size = sock.send(con._wbuf)
  2154. con._wbuf = con._wbuf[size:]
  2155. except socket.error:
  2156. pass
  2157. self._tick()
  2158.  
  2159. @classmethod
  2160. def easy_start(cl, rooms = None, name = None, password = None, pm = True):
  2161. """
  2162. Prompts the user for missing info, then starts.
  2163.  
  2164. @type rooms: list
  2165. @param room: rooms to join
  2166. @type name: str
  2167. @param name: name to join as ("" = None, None = unspecified)
  2168. @type password: str
  2169. @param password: password to join with ("" = None, None = unspecified)
  2170. """
  2171. if not rooms: rooms = str(input("Room names separated by semicolons: ")).split(";")
  2172. if len(rooms) == 1 and rooms[0] == "": rooms = []
  2173. if not name: name = str(input("User name: "))
  2174. if name == "": name = None
  2175. if not password: password = str(input("User password: "))
  2176. if password == "": password = None
  2177. self = cl(name, password, pm = pm)
  2178. for room in rooms:
  2179. self.joinRoom(room)
  2180. self.main()
  2181.  
  2182. def stop(self):
  2183. for conn in list(self._rooms.values()):
  2184. conn.disconnect()
  2185. self._running = False
  2186.  
  2187. ####
  2188. # Commands
  2189. ####
  2190. def enableBg(self):
  2191. """Enable background if available."""
  2192. self.user._mbg = True
  2193. for room in self.rooms:
  2194. room.setBgMode(1)
  2195.  
  2196. def disableBg(self):
  2197. """Disable background."""
  2198. self.user._mbg = False
  2199. for room in self.rooms:
  2200. room.setBgMode(0)
  2201.  
  2202. def enableRecording(self):
  2203. """Enable recording if available."""
  2204. self.user._mrec = True
  2205. for room in self.rooms:
  2206. room.setRecordingMode(1)
  2207.  
  2208. def disableRecording(self):
  2209. """Disable recording."""
  2210. self.user._mrec = False
  2211. for room in self.rooms:
  2212. room.setRecordingMode(0)
  2213.  
  2214. def setNameColor(self, color3x):
  2215. """
  2216. Set name color.
  2217.  
  2218. @type color3x: str
  2219. @param color3x: a 3-char RGB hex code for the color
  2220. """
  2221. self.user._nameColor = color3x
  2222.  
  2223. def setFontColor(self, color3x):
  2224. """
  2225. Set font color.
  2226.  
  2227. @type color3x: str
  2228. @param color3x: a 3-char RGB hex code for the color
  2229. """
  2230. self.user._fontColor = color3x
  2231.  
  2232. def setFontFace(self, face):
  2233. """
  2234. Set font face/family.
  2235.  
  2236. @type face: str
  2237. @param face: the font face
  2238. """
  2239. self.user._fontFace = face
  2240.  
  2241. def setFontSize(self, size):
  2242. """
  2243. Set font size.
  2244.  
  2245. @type size: int
  2246. @param size: the font size (limited: 9 to 22)
  2247. """
  2248. if size < 9: size = 9
  2249. if size > 22: size = 22
  2250. self.user._fontSize = size
  2251.  
  2252. ################################################################
  2253. # User class (well, yeah, I lied, it's actually _User)
  2254. ################################################################
  2255. _users = dict()
  2256. def User(name, *args, **kw):
  2257. if name == None: name = ""
  2258. name = name.lower()
  2259. user = _users.get(name)
  2260. if not user:
  2261. user = _User(name = name, *args, **kw)
  2262. _users[name] = user
  2263. return user
  2264.  
  2265. class _User:
  2266. """Class that represents a user."""
  2267. ####
  2268. # Init
  2269. ####
  2270. def __init__(self, name, **kw):
  2271. self._name = name.lower()
  2272. self._sids = dict()
  2273. self._msgs = list()
  2274. self._nameColor = "000"
  2275. self._fontSize = 12
  2276. self._fontFace = "0"
  2277. self._fontColor = "000"
  2278. self._mbg = False
  2279. self._mrec = False
  2280. for attr, val in kw.items():
  2281. if val == None: continue
  2282. setattr(self, "_" + attr, val)
  2283.  
  2284. ####
  2285. # Properties
  2286. ####
  2287. def _getName(self): return self._name
  2288. def _getSessionIds(self, room = None):
  2289. if room:
  2290. return self._sids.get(room, set())
  2291. else:
  2292. return set.union(*self._sids.values())
  2293. def _getRooms(self): return self._sids.keys()
  2294. def _getRoomNames(self): return [room.name for room in self._getRooms()]
  2295. def _getFontColor(self): return self._fontColor
  2296. def _getFontFace(self): return self._fontFace
  2297. def _getFontSize(self): return self._fontSize
  2298. def _getNameColor(self): return self._nameColor
  2299.  
  2300. name = property(_getName)
  2301. sessionids = property(_getSessionIds)
  2302. rooms = property(_getRooms)
  2303. roomnames = property(_getRoomNames)
  2304. fontColor = property(_getFontColor)
  2305. fontFace = property(_getFontFace)
  2306. fontSize = property(_getFontSize)
  2307. nameColor = property(_getNameColor)
  2308.  
  2309. ####
  2310. # Util
  2311. ####
  2312. def addSessionId(self, room, sid):
  2313. if room not in self._sids:
  2314. self._sids[room] = set()
  2315. self._sids[room].add(sid)
  2316.  
  2317. def removeSessionId(self, room, sid):
  2318. try:
  2319. self._sids[room].remove(sid)
  2320. if len(self._sids[room]) == 0:
  2321. del self._sids[room]
  2322. except KeyError:
  2323. pass
  2324.  
  2325. def clearSessionIds(self, room):
  2326. try:
  2327. del self._sids[room]
  2328. except KeyError:
  2329. pass
  2330.  
  2331. def hasSessionId(self, room, sid):
  2332. try:
  2333. if sid in self._sids[room]:
  2334. return True
  2335. else:
  2336. return False
  2337. except KeyError:
  2338. return False
  2339.  
  2340. ####
  2341. # Repr
  2342. ####
  2343. def __repr__(self):
  2344. return "<User: %s>" %(self.name)
  2345.  
  2346. ################################################################
  2347. # Message class
  2348. ################################################################
  2349. class Message:
  2350. """Class that represents a message."""
  2351. ####
  2352. # Attach/detach
  2353. ####
  2354. def attach(self, room, msgid):
  2355. """
  2356. Attach the Message to a message id.
  2357.  
  2358. @type msgid: str
  2359. @param msgid: message id
  2360. """
  2361. if self._msgid == None:
  2362. self._room = room
  2363. self._msgid = msgid
  2364. self._room._msgs[msgid] = self
  2365.  
  2366. def detach(self):
  2367. """Detach the Message."""
  2368. if self._msgid != None and self._msgid in self._room._msgs:
  2369. del self._room._msgs[self._msgid]
  2370. self._msgid = None
  2371.  
  2372. def delete(self):
  2373. self._room.deleteMessage(self)
  2374.  
  2375. ####
  2376. # Init
  2377. ####
  2378. def __init__(self, **kw):
  2379. """init, don't overwrite"""
  2380. self._msgid = None
  2381. self._time = None
  2382. self._user = None
  2383. self._body = None
  2384. self._room = None
  2385. self._raw = ""
  2386. self._ip = None
  2387. self._unid = ""
  2388. self._puid = ""
  2389. self._uid = ""
  2390. self._nameColor = "000"
  2391. self._fontSize = 12
  2392. self._fontFace = "0"
  2393. self._fontColor = "000"
  2394. for attr, val in kw.items():
  2395. if val == None: continue
  2396. setattr(self, "_" + attr, val)
  2397.  
  2398. ####
  2399. # Properties
  2400. ####
  2401. def _getId(self): return self._msgid
  2402. def _getTime(self): return self._time
  2403. def _getUser(self): return self._user
  2404. def _getBody(self): return self._body
  2405. def _getIP(self): return self._ip
  2406. def _getFontColor(self): return self._fontColor
  2407. def _getFontFace(self): return self._fontFace
  2408. def _getFontSize(self): return self._fontSize
  2409. def _getNameColor(self): return self._nameColor
  2410. def _getRoom(self): return self._room
  2411. def _getRaw(self): return self._raw
  2412. def _getUnid(self): return self._unid
  2413. def _getPuid(self): return self._puid
  2414.  
  2415. msgid = property(_getId)
  2416. time = property(_getTime)
  2417. user = property(_getUser)
  2418. body = property(_getBody)
  2419. room = property(_getRoom)
  2420. ip = property(_getIP)
  2421. fontColor = property(_getFontColor)
  2422. fontFace = property(_getFontFace)
  2423. fontSize = property(_getFontSize)
  2424. raw = property(_getRaw)
  2425. nameColor = property(_getNameColor)
  2426. unid = property(_getUnid)
  2427. puid = property(_getPuid)
  2428. uid = property(_getPuid) # other library use uid so we create an alias
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement