Advertisement
Guest User

Untitled

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