"ch.py" By SlackerFS (https://pastebin.com/u/SlackerFS) URL: https://pastebin.com/YxVj9YRy Created on: Thursday 16th of January 2014 07:09:35 PM CDT Retrieved on: Saturday 31 of October 2020 04:54:50 PM UTC ################################################################ # File: ch.py # Title: Chatango Library # Author: Lumirayz/Lumz # Version: 1.3.4a # Description: # An event-based library for connecting to one or multiple Chatango rooms, has # support for several things including: messaging, message font, # name color, deleting, banning, recent history, 2 userlist modes, # flagging, avoiding flood bans, detecting flags. ################################################################ ################################################################ # License ################################################################ # Copyright 2011 Lumirayz # This program is distributed under the terms of the GNU GPL. ################################################################ # Imports ################################################################ import socket import threading import time import random import re import sys import select ################################################################ # Python 2 compatibility ################################################################ if sys.version_info[0] < 3: class urllib: parse = __import__("urllib") request = __import__("urllib2") input = raw_input import codecs else: import urllib.request import urllib.parse ################################################################ # Constants ################################################################ Userlist_Recent = 0 Userlist_All = 1 BigMessage_Multiple = 0 BigMessage_Cut = 1 ################################################################ # Struct class ################################################################ class Struct: def __init__(self, **entries): self.__dict__.update(entries) ################################################################ # Tagserver stuff ################################################################ 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} 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]] def getServer(group): """ Get the server host for a certain room. @type group: str @param group: room name @rtype: str @return: the server's hostname """ try: sn = specials[group] except KeyError: group = group.replace("_", "q") group = group.replace("-", "q") fnv = float(int(group[0:min(5, len(group))], 36)) lnv = group[6: (6 + min(3, len(group) - 5))] if(lnv): lnv = float(int(lnv, 36)) lnv = max(lnv,1000) else: lnv = 1000 num = (fnv % lnv) / lnv maxnum = sum(map(lambda x: x[1], tsweights)) cumfreq = 0 sn = 0 for wgt in tsweights: cumfreq += float(wgt[1]) / maxnum if(num <= cumfreq): sn = int(wgt[0]) break return "s" + str(sn) + ".chatango.com" ################################################################ # Uid ################################################################ def genUid(): return str(random.randrange(10 ** 15, 10 ** 16)) ################################################################ # Message stuff ################################################################ def clean_message(msg): """ Clean a message and return the message, n tag and f tag. @type msg: str @param msg: the message @rtype: str, str, str @returns: cleaned message, n tag contents, f tag contents """ n = re.search("", msg) if n: n = n.group(1) f = re.search("", msg) if f: f = f.group(1) msg = re.sub("", "", msg) msg = re.sub("", "", msg) msg = strip_html(msg) msg = msg.replace("<", "<") msg = msg.replace(">", ">") msg = msg.replace(""", "\"") msg = msg.replace("'", "'") msg = msg.replace("&", "&") return msg, n, f def strip_html(msg): """Strip HTML.""" li = msg.split("<") if len(li) == 1: return li[0] else: ret = list() for data in li: data = data.split(">", 1) if len(data) == 1: ret.append(data[0]) elif len(data) == 2: ret.append(data[1]) return "".join(ret) def parseNameColor(n): """This just returns its argument, should return the name color.""" #probably is already the name return n def parseFont(f): """Parses the contents of a f tag and returns color, face and size.""" #' xSZCOL="FONT"' try: #TODO: remove quick hack sizecolor, fontface = f.split("=", 1) sizecolor = sizecolor.strip() size = int(sizecolor[1:3]) col = sizecolor[3:6] if col == "": col = None face = f.split("\"", 2)[1] return col, face, size except: return None, None, None ################################################################ # Anon id ################################################################ def getAnonId(n, ssid): """Gets the anon's id.""" if n == None: n = "5504" try: return "".join(list( map(lambda x: str(x[0] + x[1])[-1], list(zip( list(map(lambda x: int(x), n)), list(map(lambda x: int(x), ssid[4:])) ))) )) except ValueError: return "NNNN" ################################################################ # PM Auth ################################################################ auth_re = re.compile(r"auth\.chatango\.com ?= ?([^;]*)", re.IGNORECASE) def _getAuth(name, password): """ Request an auid using name and password. @type name: str @param name: name @type password: str @param password: password @rtype: str @return: auid """ data = urllib.parse.urlencode({ "user_id": name, "password": password, "storecookie": "on", "checkerrors": "yes" }).encode() try: resp = urllib.request.urlopen("http://chatango.com/login", data) headers = resp.headers except Exception: return None for header, value in headers.items(): if header.lower() == "set-cookie": m = auth_re.search(value) if m: auth = m.group(1) if auth == "": return None return auth return None ################################################################ # PM class ################################################################ class PM: """Manages a connection with Chatango PM.""" #### # Init #### def __init__(self, mgr): self._connected = False self._mgr = mgr self._auid = None self._blocklist = set() self._contacts = set() self._wlock = False self._firstCommand = True self._wbuf = b"" self._wlockbuf = b"" self._rbuf = b"" self._pingTask = None self._connect() if sys.version_info[0] < 3 and sys.platform.startswith("win"): self.unicodeCompat = False else: self.unicodeCompat = True #### # Connections #### def _connect(self): self._wbuf = b"" self._sock = socket.socket() self._sock.connect((self._mgr._PMHost, self._mgr._PMPort)) self._sock.setblocking(False) self._firstCommand = True if not self._auth(): return self._pingTask = self.mgr.setInterval(self._mgr._pingDelay, self.ping) self._connected = True def _auth(self): self._auid = _getAuth(self._mgr.name, self._mgr.password) if self._auid == None: self._sock.close() self._callEvent("onLoginFail") self._sock = None return False self._sendCommand("tlogin", self._auid, "2") self._setWriteLock(True) return True def disconnect(self): self._disconnect() self._callEvent("onPMDisconnect") def _disconnect(self): self._connected = False self._sock.close() self._sock = None #### # Feed #### def _feed(self, data): """ Feed data to the connection. @type data: bytes @param data: data to be fed """ self._rbuf += data while self._rbuf.find(b"\x00") != -1: data = self._rbuf.split(b"\x00") for food in data[:-1]: if self.unicodeCompat: self._process(food.decode().rstrip("\r\n")) else: self._process(food.decode(errors="replace").rstrip("\r\n")) self._rbuf = data[-1] def _process(self, data): """ Process a command string. @type data: str @param data: the command string """ self._callEvent("onRaw", data) data = data.split(":") cmd, args = data[0], data[1:] func = "rcmd_" + cmd if hasattr(self, func): getattr(self, func)(args) #### # Properties #### def getManager(self): return self._mgr def getContacts(self): return self._contacts def getBlocklist(self): return self._blocklist mgr = property(getManager) contacts = property(getContacts) blocklist = property(getBlocklist) #### # Received Commands #### def rcmd_OK(self, args): self._setWriteLock(False) self._sendCommand("wl") self._sendCommand("getblock") self._callEvent("onPMConnect") def rcmd_wl(self, args): self._contacts = set() for i in range(len(args) // 4): name, last_on, is_on, idle = args[i * 4: i * 4 + 4] user = User(name) self._contacts.add(user) self._callEvent("onPMContactlistReceive") def rcmd_block_list(self, args): self._blocklist = set() for name in args: if name == "": continue self._blocklist.add(User(name)) def rcmd_DENIED(self, args): self._disconnect() self._callEvent("onLoginFail") def rcmd_msg(self, args): user = User(args[0]) body = strip_html(":".join(args[5:])) if self.unicodeCompat else strip_html(":".join(args[5:])) .encode("ascii","ignore").decode("ascii") self._callEvent("onPMMessage", user, body) def rcmd_msgoff(self, args): user = User(args[0]) body = strip_html(":".join(args[5:])) self._callEvent("onPMOfflineMessage", user, body) def rcmd_wlonline(self, args): self._callEvent("onPMContactOnline", User(args[0])) def rcmd_wloffline(self, args): self._callEvent("onPMContactOffline", User(args[0])) def rcmd_kickingoff(self, args): self.disconnect() #### # Commands #### def ping(self): self._sendCommand("") self._callEvent("onPMPing") def message(self, user, msg): if msg!=None: self._sendCommand("msg", user.name, msg) def addContact(self, user): if user not in self._contacts: self._sendCommand("wladd", user.name) self._contacts.add(user) self._callEvent("onPMContactAdd", user) def removeContact(self, user): if user in self._contacts: self._sendCommand("wldelete", user.name) self._contacts.remove(user) self._callEvent("onPMContactRemove", user) def block(self, user): if user not in self._blocklist: self._sendCommand("block", user.name) self._block.remove(user) self._callEvent("onPMBlock", user) def unblock(self, user): if user in self._blocklist: self._sendCommand("unblock", user.name) self._block.remove(user) self._callEvent("onPMUnblock", user) #### # Util #### def _callEvent(self, evt, *args, **kw): getattr(self.mgr, evt)(self, *args, **kw) self.mgr.onEventCalled(self, evt, *args, **kw) def _write(self, data): if self._wlock: self._wlockbuf += data else: self.mgr._write(self, data) def _setWriteLock(self, lock): self._wlock = lock if self._wlock == False: self._write(self._wlockbuf) self._wlockbuf = b"" def _sendCommand(self, *args): """ Send a command. @type args: [str, str, ...] @param args: command and list of arguments """ if self._firstCommand: terminator = b"\x00" self._firstCommand = False else: terminator = b"\r\n\x00" self._write(":".join(args).encode() + terminator) ################################################################ # Room class ################################################################ class Room: """Manages a connection with a Chatango room.""" #### # Init #### def __init__(self, room, uid = None, server = None, port = None, mgr = None): # Basic stuff self._name = room self._server = server or getServer(room) self._port = port or 443 self._mgr = mgr # Under the hood self._connected = False self._reconnecting = False self._uid = uid or genUid() self._rbuf = b"" self._wbuf = b"" self._wlockbuf = b"" self._owner = None self._mods = set() self._mqueue = dict() self._history = list() self._userlist = list() self._firstCommand = True self._connectAmmount = 0 self._premium = False self._userCount = 0 self._pingTask = None self._botname = None self._currentname = None self._users = dict() self._msgs = dict() self._wlock = False self._silent = False self._banlist = list() if sys.version_info[0] < 3 and sys.platform.startswith("win"): self.unicodeCompat = False else: self.unicodeCompat = True # Inited vars if self._mgr: self._connect() #### # User and Message management #### def getMessage(self, mid): return self._msgs.get(mid) def createMessage(self, msgid, **kw): if msgid not in self._msgs: msg = Message(msgid = msgid, **kw) self._msgs[msgid] = msg else: msg = self._msgs[msgid] return msg #### # Connect/disconnect #### def _connect(self): """Connect to the server.""" self._sock = socket.socket() self._sock.connect((self._server, self._port)) self._sock.setblocking(False) self._firstCommand = True self._wbuf = b"" self._auth() self._pingTask = self.mgr.setInterval(self.mgr._pingDelay, self.ping) if not self._reconnecting: self.connected = True def reconnect(self): """Reconnect.""" self._reconnect() def _reconnect(self): """Reconnect.""" self._reconnecting = True if self.connected: self._disconnect() self._uid = genUid() self._connect() self._reconnecting = False def disconnect(self): """Disconnect.""" self._disconnect() self._callEvent("onDisconnect") def _disconnect(self): """Disconnect from the server.""" if not self._reconnecting: self.connected = False for user in self._userlist: user.clearSessionIds(self) self._userlist = list() self._pingTask.cancel() self._sock.close() if not self._reconnecting: del self.mgr._rooms[self.name] def _auth(self): """Authenticate.""" # login as name with password if self.mgr.name and self.mgr.password: self._sendCommand("bauth", self.name, self._uid, self.mgr.name, self.mgr.password) self._currentname = self.mgr.name # login as anon else: self._sendCommand("bauth", self.name) self._setWriteLock(True) #### # Properties #### def getName(self): return self._name def getBotName(self): if self.mgr.name and self.mgr.password: return self.mgr.name elif self.mgr.name and self.mgr.password == None: return "#" + self.mgr.name elif self.mgr.name == None: return self._botname def getCurrentname(self): return self._currentname def getManager(self): return self._mgr def getUserlist(self, mode = None, unique = None, memory = None): ul = None if mode == None: mode = self.mgr._userlistMode if unique == None: unique = self.mgr._userlistUnique if memory == None: memory = self.mgr._userlistMemory if mode == Userlist_Recent: ul = map(lambda x: x.user, self._history[-memory:]) elif mode == Userlist_All: ul = self._userlist if unique: return list(set(ul)) else: return ul def getUserNames(self): ul = self.userlist return list(map(lambda x: x.name, ul)) def getUser(self): return self.mgr.user def getOwner(self): return self._owner def getOwnerName(self): return self._owner.name def getMods(self): newset = set() for mod in self._mods: newset.add(mod) return newset def getModNames(self): mods = self.getMods() return [x.name for x in mods] def getUserCount(self): return self._userCount def getSilent(self): return self._silent def setSilent(self, val): self._silent = val def getBanlist(self): return [record[2] for record in self._banlist] name = property(getName) botname = property(getBotName) currentname = property(getCurrentname) mgr = property(getManager) userlist = property(getUserlist) usernames = property(getUserNames) user = property(getUser) owner = property(getOwner) ownername = property(getOwnerName) mods = property(getMods) modnames = property(getModNames) usercount = property(getUserCount) silent = property(getSilent, setSilent) banlist = property(getBanlist) #### # Feed/process #### def _feed(self, data): """ Feed data to the connection. @type data: bytes @param data: data to be fed """ self._rbuf += data while self._rbuf.find(b"\x00") != -1: data = self._rbuf.split(b"\x00") for food in data[:-1]: if self.unicodeCompat: self._process(food.decode().rstrip("\r\n")) else: self._process(food.decode(errors="replace").rstrip("\r\n")) self._rbuf = data[-1] def _process(self, data): """ Process a command string. @type data: str @param data: the command string """ self._callEvent("onRaw", data) data = data.split(":") cmd, args = data[0], data[1:] func = "rcmd_" + cmd if hasattr(self, func): getattr(self, func)(args) #### # Received Commands #### def rcmd_ok(self, args): # if no name, join room as anon and no password if args[2] == "N" and self.mgr.password == None and self.mgr.name == None: n = args[4].rsplit('.', 1)[0] n = n[-4:] aid = args[1][0:8] pid = "!anon" + getAnonId(n, aid) self._botname = pid self._currentname = pid self.user._nameColor = n # if got name, join room as name and no password elif args[2] == "N" and self.mgr.password == None: self._sendCommand("blogin", self.mgr.name) self._currentname = self.mgr.name # if got password but fail to login elif args[2] != "M": #unsuccesful login self._callEvent("onLoginFail") self.disconnect() self._owner = User(args[0]) self._uid = args[1] self._aid = args[1][4:8] self._mods = set(map(lambda x: User(x), args[6].split(";"))) self._i_log = list() def rcmd_denied(self, args): self._disconnect() self._callEvent("onConnectFail") self._callEvent("onStartJoin", "denied") def rcmd_inited(self, args): self._sendCommand("g_participants", "start") self._sendCommand("getpremium", "1") self.requestBanlist() if self._connectAmmount == 0: self._callEvent("onConnect") for msg in reversed(self._i_log): user = msg.user self._callEvent("onHistoryMessage", user, msg) self._addHistory(msg) del self._i_log self._callEvent("onStartJoin", "ok") else: self._callEvent("onReconnect") self._connectAmmount += 1 self._setWriteLock(False) def rcmd_premium(self, args): if float(args[1]) > time.time(): self._premium = True if self.user._mbg: self.setBgMode(1) if self.user._mrec: self.setRecordingMode(1) else: self._premium = False def rcmd_mods(self, args): modnames = args mods = set(map(lambda x: User(x), modnames)) premods = self._mods for user in mods - premods: #modded self._mods.add(user) self._callEvent("onModAdd", user) for user in premods - mods: #demodded self._mods.remove(user) self._callEvent("onModRemove", user) self._callEvent("onModChange") def rcmd_b(self, args): mtime = float(args[0]) puid = args[3] ip = args[6] name = args[1] rawmsg = ":".join(args[9:]) if self.unicodeCompat else ":".join(args[9:]).encode("ascii","ignore").decode("ascii") msg, n, f = clean_message(rawmsg) if name == "": nameColor = None name = "#" + args[2] if name == "#": name = "!anon" + getAnonId(n, puid) else: if n: nameColor = parseNameColor(n) else: nameColor = None i = args[5] unid = args[4] #Create an anonymous message and queue it because msgid is unknown. if f: fontColor, fontFace, fontSize = parseFont(f) else: fontColor, fontFace, fontSize = None, None, None msg = Message( time = mtime, user = User(name), body = msg, raw = rawmsg, ip = ip, nameColor = nameColor, fontColor = fontColor, fontFace = fontFace, fontSize = fontSize, unid = unid, room = self ) self._mqueue[i] = msg def rcmd_u(self, args): temp = Struct(**self._mqueue) if hasattr(temp, args[0]): msg = getattr(temp, args[0]) if msg.user != self.user: msg.user._fontColor = msg.fontColor msg.user._fontFace = msg.fontFace msg.user._fontSize = msg.fontSize msg.user._nameColor = msg.nameColor del self._mqueue[args[0]] msg.attach(self, args[1]) self._addHistory(msg) self._callEvent("onMessage", msg.user, msg) def rcmd_i(self, args): mtime = float(args[0]) puid = args[3] ip = args[6] if ip == "": ip = None name = args[1] rawmsg = ":".join(args[8:]) msg, n, f = clean_message(rawmsg) msgid = args[5] if name == "": nameColor = None name = "#" + args[2] if name == "#": name = "!anon" + getAnonId(n, puid) else: if n: nameColor = parseNameColor(n) else: nameColor = None if f: fontColor, fontFace, fontSize = parseFont(f) else: fontColor, fontFace, fontSize = None, None, None msg = self.createMessage( msgid = msgid, time = mtime, user = User(name), body = msg, raw = rawmsg, ip = args[6], unid = args[4], nameColor = nameColor, fontColor = fontColor, fontFace = fontFace, fontSize = fontSize, room = self ) if msg.user != self.user: msg.user._fontColor = msg.fontColor msg.user._fontFace = msg.fontFace msg.user._fontSize = msg.fontSize msg.user._nameColor = msg.nameColor self._i_log.append(msg) def rcmd_g_participants(self, args): args = ":".join(args) args = args.split(";") for data in args: data = data.split(":") name = data[3].lower() if name == "none": continue user = User( name = name, room = self ) user.addSessionId(self, data[0]) self._userlist.append(user) def rcmd_participant(self, args): if args[0] == "0": #leave name = args[3].lower() if name == "none": return user = User(name) user.removeSessionId(self, args[1]) self._userlist.remove(user) if user not in self._userlist or not self.mgr._userlistEventUnique: self._callEvent("onLeave", user) else: #join name = args[3].lower() if name == "none": return user = User( name = name, room = self ) user.addSessionId(self, args[1]) if user not in self._userlist: doEvent = True else: doEvent = False self._userlist.append(user) if doEvent or not self.mgr._userlistEventUnique: self._callEvent("onJoin", user) def rcmd_show_fw(self, args): self._callEvent("onFloodWarning") def rcmd_show_tb(self, args): self._callEvent("onFloodBan") def rcmd_tb(self, args): self._callEvent("onFloodBanRepeat") def rcmd_delete(self, args): msg = self.getMessage(args[0]) if msg: if msg in self._history: self._history.remove(msg) self._callEvent("onMessageDelete", msg.user, msg) msg.detach() def rcmd_deleteall(self, args): for msgid in args: self.rcmd_delete([msgid]) def rcmd_n(self, args): self._userCount = int(args[0], 16) self._callEvent("onUserCountChange") def rcmd_blocklist(self, args): self._banlist = list() sections = ":".join(args).split(";") for section in sections: params = section.split(":") if len(params) != 5: continue if params[2] == "": continue self._banlist.append(( params[0], #unid params[1], #ip User(params[2]), #target float(params[3]), #time User(params[4]) #src )) self._callEvent("onBanlistUpdate") def rcmd_blocked(self, args): if args[2] == "": return target = User(args[2]) user = User(args[3]) self._banlist.append((args[0], args[1], target, float(args[4]), user)) self._callEvent("onBan", user, target) self.requestBanlist() def rcmd_unblocked(self, args): if args[2] == "": return target = User(args[2]) self._callEvent("onUnban", user, target) self.requestBanlist() #### # Commands #### def login(self, NAME, PASS = None): if PASS: self._sendCommand("blogin", NAME, PASS) else: self._sendCommand("blogin", NAME) self._currentname = NAME def logout(self): self._sendCommand("blogout") self._currentname = self._botname def ping(self): """Send a ping.""" self._sendCommand("") self._callEvent("onPing") def rawMessage(self, msg): """ Send a message without n and f tags. @type msg: str @param msg: message """ if not self._silent: self._sendCommand("bmsg:tl2r", msg) def message(self, msg, html = False): """ Send a message. @type msg: str @param msg: message """ if msg==None: return msg = msg.rstrip() if not html: msg = msg.replace("<", "<").replace(">", ">") if len(msg) > self.mgr._maxLength: if self.mgr._tooBigMessage == BigMessage_Cut: self.message(msg[:self.mgr._maxLength], html = html) elif self.mgr._tooBigMessage == BigMessage_Multiple: while len(msg) > 0: sect = msg[:self.mgr._maxLength] msg = msg[self.mgr._maxLength:] self.message(sect, html = html) return msg = "" + msg if self._currentname != None or not self._currentname.startswith("!anon"): msg = "" %(self.user.fontSize, self.user.fontColor, self.user.fontFace) + msg self.rawMessage(msg) def setBgMode(self, mode): self._sendCommand("msgbg", str(mode)) def setRecordingMode(self, mode): self._sendCommand("msgmedia", str(mode)) def addMod(self, user): """ Add a moderator. @type user: User @param user: User to mod. """ if self.getLevel(self.user) == 2: self._sendCommand("addmod", user.name) def removeMod(self, user): """ Remove a moderator. @type user: User @param user: User to demod. """ if self.getLevel(self.user) == 2: self._sendCommand("removemod", user.name) def flag(self, message): """ Flag a message. @type message: Message @param message: message to flag """ self._sendCommand("g_flag", message.msgid) def flagUser(self, user): """ Flag a user. @type user: User @param user: user to flag @rtype: bool @return: whether a message to flag was found """ msg = self.getLastMessage(user) if msg: self.flag(msg) return True return False def delete(self, message): """ Delete a message. (Moderator only) @type message: Message @param message: message to delete """ if self.getLevel(self.user) > 0: self._sendCommand("delmsg", message.msgid) def rawClearUser(self, unid): self._sendCommand("delallmsg", unid) def clearUser(self, user): """ Clear all of a user's messages. (Moderator only) @type user: User @param user: user to delete messages of @rtype: bool @return: whether a message to delete was found """ if self.getLevel(self.user) > 0: msg = self.getLastMessage(user) if msg: self.rawClearUser(msg.unid) return True return False def clearall(self): """Clear all messages. (Owner only)""" if self.getLevel(self.user) == 2: self._sendCommand("clearall") def rawBan(self, name, ip, unid): """ Execute the block command using specified arguments. (For advanced usage) @type name: str @param name: name @type ip: str @param ip: ip address @type unid: str @param unid: unid """ self._sendCommand("block", unid, ip, name) def ban(self, msg): """ Ban a message's sender. (Moderator only) @type message: Message @param message: message to ban sender of """ if self.getLevel(self.user) > 0: self.rawBan(msg.user.name, msg.ip, msg.unid) def banUser(self, user): """ Ban a user. (Moderator only) @type user: User @param user: user to ban @rtype: bool @return: whether a message to ban the user was found """ msg = self.getLastMessage(user) if msg: self.ban(msg) return True return False def requestBanlist(self): """Request an updated banlist.""" self._sendCommand("blocklist", "block", "", "next", "500") def rawUnban(self, name, ip, unid): """ Execute the unblock command using specified arguments. (For advanced usage) @type name: str @param name: name @type ip: str @param ip: ip address @type unid: str @param unid: unid """ self._sendCommand("removeblock", unid, ip, name) def unban(self, user): """ Unban a user. (Moderator only) @type user: User @param user: user to unban @rtype: bool @return: whether it succeeded """ rec = self._getBanRecord(user) if rec: self.rawUnban(rec[2].name, rec[1], rec[0]) return True else: return False #### # Util #### def _getBanRecord(self, user): for record in self._banlist: if record[2] == user: return record return None def _callEvent(self, evt, *args, **kw): getattr(self.mgr, evt)(self, *args, **kw) self.mgr.onEventCalled(self, evt, *args, **kw) def _write(self, data): if self._wlock: self._wlockbuf += data else: self.mgr._write(self, data) def _setWriteLock(self, lock): self._wlock = lock if self._wlock == False: self._write(self._wlockbuf) self._wlockbuf = b"" def _sendCommand(self, *args): """ Send a command. @type args: [str, str, ...] @param args: command and list of arguments """ if self._firstCommand: terminator = b"\x00" self._firstCommand = False else: terminator = b"\r\n\x00" self._write(":".join(args).encode() + terminator) def getLevel(self, user): if user == self._owner: return 2 if user in self._mods: return 1 return 0 def getLastMessage(self, user = None): if user: try: i = 1 while True: msg = self._history[-i] if msg.user == user: return msg i += 1 except IndexError: return None else: try: return self._history[-1] except IndexError: return None return None def findUser(self, name): name = name.lower() ul = self.getUserlist() udi = dict(zip([u.name for u in ul], ul)) cname = None for n in udi.keys(): if n.find(name) != -1: if cname: return None #ambiguous!! cname = n if cname: return udi[cname] else: return None #### # History #### def _addHistory(self, msg): """ Add a message to history. @type msg: Message @param msg: message """ self._history.append(msg) if len(self._history) > self.mgr._maxHistoryLength: rest, self._history = self._history[:-self.mgr._maxHistoryLength], self._history[-self.mgr._maxHistoryLength:] for msg in rest: msg.detach() ################################################################ # RoomManager class ################################################################ class RoomManager: """Class that manages multiple connections.""" #### # Config #### _Room = Room _PM = PM _PMHost = "c1.chatango.com" _PMPort = 5222 _TimerResolution = 0.2 #at least x times per second _pingDelay = 20 _userlistMode = Userlist_Recent _userlistUnique = True _userlistMemory = 50 _userlistEventUnique = False _tooBigMessage = BigMessage_Multiple _maxLength = 1800 _maxHistoryLength = 150 #### # Init #### def __init__(self, name = None, password = None, pm = True): self._name = name self._password = password self._running = False self._tasks = set() self._rooms = dict() self._rooms_copy = list() if pm: self._pm = self._PM(mgr = self) else: self._pm = None #### # Join/leave #### def joinRoom(self, room): """ Join a room or return None if already joined. @type room: str @param room: room to join @rtype: Room or None @return: the room or nothing """ room = room.lower() if room not in self._rooms: con = self._Room(room, mgr = self) self._rooms[room] = con return con else: return None def leaveRoom(self, room): """ Leave a room. @type room: str @param room: room to leave """ room = room.lower() if room in self._rooms: con = self._rooms[room] con.disconnect() def getRoom(self, room): """ Get room with a name, or None if not connected to this room. @type room: str @param room: room @rtype: Room @return: the room """ room = room.lower() if room in self._rooms: return self._rooms[room] else: return None #### # Properties #### def getUser(self): return User(self._name) def getName(self): return self._name def getPassword(self): return self._password def getRooms(self): return set(self._rooms.values()) def getRoomNames(self): return set(self._rooms.keys()) def getRooms_copy(self): return set(self._rooms_copy) def getPM(self): return self._pm user = property(getUser) name = property(getName) password = property(getPassword) rooms = property(getRooms) roomnames = property(getRoomNames) rooms_copy = property(getRooms_copy) pm = property(getPM) #### # Virtual methods #### def onInit(self): """Called on init.""" pass def safePrint(self, text): """ use this to safely print text with unicode""" while True: try: print(text) break except UnicodeEncodeError as ex: text = (text[0:ex.start]+'(unicode)'+text[ex.end:]) def onStartJoin(self, room, status): """Don't edit unless you know what you are doing""" if status == "ok": if self._rooms_copy == []: pass elif len(self._rooms_copy) > 0: self.joinRoom(self._rooms_copy.pop()) elif status == "denied": # if it fail to connect, skip it if self._rooms_copy == []: pass elif len(self._rooms_copy) > 0: self.joinRoom(self._rooms_copy.pop()) def onConnect(self, room): """ Called when connected to the room. @type room: Room @param room: room where the event occured """ pass def onReconnect(self, room): """ Called when reconnected to the room. @type room: Room @param room: room where the event occured """ pass def onConnectFail(self, room): """ Called when the connection failed. @type room: Room @param room: room where the event occured """ pass def onDisconnect(self, room): """ Called when the client gets disconnected. @type room: Room @param room: room where the event occured """ pass def onLoginFail(self, room): """ Called on login failure, disconnects after. @type room: Room @param room: room where the event occured """ pass def onFloodBan(self, room): """ Called when either flood banned or flagged. @type room: Room @param room: room where the event occured """ pass def onFloodBanRepeat(self, room): """ Called when trying to send something when floodbanned. @type room: Room @param room: room where the event occured """ pass def onFloodWarning(self, room): """ Called when an overflow warning gets received. @type room: Room @param room: room where the event occured """ pass def onMessageDelete(self, room, user, message): """ Called when a message gets deleted. @type room: Room @param room: room where the event occured @type user: User @param user: owner of deleted message @type message: Message @param message: message that got deleted """ pass def onModChange(self, room): """ Called when the moderator list changes. @type room: Room @param room: room where the event occured """ pass def onModAdd(self, room, user): """ Called when a moderator gets added. @type room: Room @param room: room where the event occured """ pass def onModRemove(self, room, user): """ Called when a moderator gets removed. @type room: Room @param room: room where the event occured """ pass def onMessage(self, room, user, message): """ Called when a message gets received. @type room: Room @param room: room where the event occured @type user: User @param user: owner of message @type message: Message @param message: received message """ pass def onHistoryMessage(self, room, user, message): """ Called when a message gets received from history. @type room: Room @param room: room where the event occured @type user: User @param user: owner of message @type message: Message @param message: the message that got added """ pass def onJoin(self, room, user): """ Called when a user joins. Anonymous users get ignored here. @type room: Room @param room: room where the event occured @type user: User @param user: the user that has joined """ pass def onLeave(self, room, user): """ Called when a user leaves. Anonymous users get ignored here. @type room: Room @param room: room where the event occured @type user: User @param user: the user that has left """ pass def onRaw(self, room, raw): """ Called before any command parsing occurs. @type room: Room @param room: room where the event occured @type raw: str @param raw: raw command data """ pass def onPing(self, room): """ Called when a ping gets sent. @type room: Room @param room: room where the event occured """ pass def onUserCountChange(self, room): """ Called when the user count changes. @type room: Room @param room: room where the event occured """ pass def onBan(self, room, user, target): """ Called when a user gets banned. @type room: Room @param room: room where the event occured @type user: User @param user: user that banned someone @type target: User @param target: user that got banned """ pass def onUnban(self, room, user, target): """ Called when a user gets unbanned. @type room: Room @param room: room where the event occured @type user: User @param user: user that unbanned someone @type target: User @param target: user that got unbanned """ pass def onBanlistUpdate(self, room): """ Called when a banlist gets updated. @type room: Room @param room: room where the event occured """ pass def onPMConnect(self, pm): pass def onPMDisconnect(self, pm): pass def onPMPing(self, pm): pass def onPMMessage(self, pm, user, body): pass def onPMOfflineMessage(self, pm, user, body): pass def onPMContactlistReceive(self, pm): pass def onPMBlocklistReceive(self, pm): pass def onPMContactAdd(self, pm, user): pass def onPMContactRemove(self, pm, user): pass def onPMBlock(self, pm, user): pass def onPMUnblock(self, pm, user): pass def onPMContactOnline(self, pm, user): pass def onPMContactOffline(self, pm, user): pass def onEventCalled(self, room, evt, *args, **kw): """ Called on every room-based event. @type room: Room @param room: room where the event occured @type evt: str @param evt: the event """ pass #### # Deferring #### def deferToThread(self, callback, func, *args, **kw): """ Defer a function to a thread and callback the return value. @type callback: function @param callback: function to call on completion @type cbargs: tuple or list @param cbargs: arguments to get supplied to the callback @type func: function @param func: function to call """ def f(func, callback, *args, **kw): ret = func(*args, **kw) self.setTimeout(0, callback, ret) threading._start_new_thread(f, (func, callback) + args, kw) #### # Scheduling #### class _Task: def cancel(self): """Sugar for removeTask.""" self.mgr.removeTask(self) def _tick(self): now = time.time() for task in set(self._tasks): if task.target <= now: task.func(*task.args, **task.kw) if task.isInterval: task.target = now + task.timeout else: self._tasks.remove(task) def setTimeout(self, timeout, func, *args, **kw): """ Call a function after at least timeout seconds with specified arguments. @type timeout: int @param timeout: timeout @type func: function @param func: function to call @rtype: _Task @return: object representing the task """ task = self._Task() task.mgr = self task.target = time.time() + timeout task.timeout = timeout task.func = func task.isInterval = False task.args = args task.kw = kw self._tasks.add(task) return task def setInterval(self, timeout, func, *args, **kw): """ Call a function at least every timeout seconds with specified arguments. @type timeout: int @param timeout: timeout @type func: function @param func: function to call @rtype: _Task @return: object representing the task """ task = self._Task() task.mgr = self task.target = time.time() + timeout task.timeout = timeout task.func = func task.isInterval = True task.args = args task.kw = kw self._tasks.add(task) return task def removeTask(self, task): """ Cancel a task. @type task: _Task @param task: task to cancel """ self._tasks.remove(task) #### # Util #### def _write(self, room, data): room._wbuf += data def getConnections(self): li = list(self._rooms.values()) if self._pm: li.append(self._pm) return [c for c in li if c._sock != None] #### # Main #### def main(self): self.onInit() self._running = True while self._running: conns = self.getConnections() socks = [x._sock for x in conns] wsocks = [x._sock for x in conns if x._wbuf != b""] rd, wr, sp = select.select(socks, wsocks, [], self._TimerResolution) for sock in rd: con = [c for c in conns if c._sock == sock][0] try: data = sock.recv(1024) if(len(data) > 0): con._feed(data) else: con.disconnect() except socket.error: pass for sock in wr: con = [c for c in conns if c._sock == sock][0] try: size = sock.send(con._wbuf) con._wbuf = con._wbuf[size:] except socket.error: pass self._tick() @classmethod def easy_start(cl, rooms = None, name = None, password = None, pm = True): """ Prompts the user for missing info, then starts. @type rooms: list @param room: rooms to join @type name: str @param name: name to join as ("" = None, None = unspecified) @type password: str @param password: password to join with ("" = None, None = unspecified) """ if not rooms: rooms = str(input("Room names separated by semicolons: ")).split(";") if len(rooms) == 1 and rooms[0] == "": rooms = [] if not name: name = str(input("User name: ")) if name == "": name = None if not password: password = str(input("User password: ")) if password == "": password = None self = cl(name, password, pm = pm) self._rooms_copy=rooms[:] if len(self._rooms_copy)>0: self.joinRoom(self._rooms_copy.pop()) self.main() def stop(self): for conn in list(self._rooms.values()): conn.disconnect() self._running = False #### # Commands #### def enableBg(self): """Enable background if available.""" self.user._mbg = True for room in self.rooms: room.setBgMode(1) def disableBg(self): """Disable background.""" self.user._mbg = False for room in self.rooms: room.setBgMode(0) def enableRecording(self): """Enable recording if available.""" self.user._mrec = True for room in self.rooms: room.setRecordingMode(1) def disableRecording(self): """Disable recording.""" self.user._mrec = False for room in self.rooms: room.setRecordingMode(0) def setNameColor(self, color3x): """ Set name color. @type color3x: str @param color3x: a 3-char RGB hex code for the color """ self.user._nameColor = color3x def setFontColor(self, color3x): """ Set font color. @type color3x: str @param color3x: a 3-char RGB hex code for the color """ self.user._fontColor = color3x def setFontFace(self, face): """ Set font face/family. @type face: str @param face: the font face """ self.user._fontFace = face def setFontSize(self, size): """ Set font size. @type size: int @param size: the font size (limited: 9 to 22) """ if size < 9: size = 9 if size > 22: size = 22 self.user._fontSize = size ################################################################ # User class (well, yeah, I lied, it's actually _User) ################################################################ _users = dict() def User(name, *args, **kw): if name == None: name = "" name = name.lower() user = _users.get(name) if not user: user = _User(name = name, *args, **kw) _users[name] = user return user class _User: """Class that represents a user.""" #### # Init #### def __init__(self, name, **kw): self._name = name.lower() self._sids = dict() self._msgs = list() self._nameColor = "000" self._fontSize = 12 self._fontFace = "0" self._fontColor = "000" self._mbg = False self._mrec = False for attr, val in kw.items(): if val == None: continue setattr(self, "_" + attr, val) #### # Properties #### def getName(self): return self._name def getSessionIds(self, room = None): if room: return self._sids.get(room, set()) else: return set.union(*self._sids.values()) def getRooms(self): return self._sids.keys() def getRoomNames(self): return [room.name for room in self.getRooms()] def getFontColor(self): return self._fontColor def getFontFace(self): return self._fontFace def getFontSize(self): return self._fontSize def getNameColor(self): return self._nameColor name = property(getName) sessionids = property(getSessionIds) rooms = property(getRooms) roomnames = property(getRoomNames) fontColor = property(getFontColor) fontFace = property(getFontFace) fontSize = property(getFontSize) nameColor = property(getNameColor) #### # Util #### def addSessionId(self, room, sid): if room not in self._sids: self._sids[room] = set() self._sids[room].add(sid) def removeSessionId(self, room, sid): try: self._sids[room].remove(sid) if len(self._sids[room]) == 0: del self._sids[room] except KeyError: pass def clearSessionIds(self, room): try: del self._sids[room] except KeyError: pass def hasSessionId(self, room, sid): try: if sid in self._sids[room]: return True else: return False except KeyError: return False #### # Repr #### def __repr__(self): return "" %(self.name) ################################################################ # Message class ################################################################ class Message: """Class that represents a message.""" #### # Attach/detach #### def attach(self, room, msgid): """ Attach the Message to a message id. @type msgid: str @param msgid: message id """ if self._msgid == None: self._room = room self._msgid = msgid self._room._msgs[msgid] = self def detach(self): """Detach the Message.""" if self._msgid != None and self._msgid in self._room._msgs: del self._room._msgs[self._msgid] self._msgid = None #### # Init #### def __init__(self, **kw): self._msgid = None self._time = None self._user = None self._body = None self._room = None self._raw = "" self._ip = None self._unid = "" self._nameColor = "000" self._fontSize = 12 self._fontFace = "0" self._fontColor = "000" for attr, val in kw.items(): if val == None: continue setattr(self, "_" + attr, val) #### # Properties #### def getId(self): return self._msgid def getTime(self): return self._time def getUser(self): return self._user def getBody(self): return self._body def getIP(self): return self._ip def getFontColor(self): return self._fontColor def getFontFace(self): return self._fontFace def getFontSize(self): return self._fontSize def getNameColor(self): return self._nameColor def getRoom(self): return self._room def getRaw(self): return self._raw def getUnid(self): return self._unid msgid = property(getId) time = property(getTime) user = property(getUser) body = property(getBody) room = property(getRoom) ip = property(getIP) fontColor = property(getFontColor) fontFace = property(getFontFace) fontSize = property(getFontSize) raw = property(getRaw) nameColor = property(getNameColor) unid = property(getUnid)