Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import ch
- import curses
- import threading
- import sys
- import base64
- import random
- import traceback
- # Chatango client by MegaLoler
- # Chatango library by Lumirayz
- # ported to python 3.2 by ASL97
- # version 0.4
- #changes:
- #default echo to false because it causes some issues
- #fixed online list! although it doesn't show up until someone says something
- #fixed a small bug that put the cursor in the wrong place when the user list updated
- #added junk to the todo list lol
- #added fast deleted/wipe to the client(need to be mod), button "#" ~~~~asl97
- #added beep to the client ~~~~ asl97
- #todo:
- #fix /part ing
- #make userlist appear immediately
- #add notation in the userlist for moderators/etc
- #make the lists on the left and right scrollable
- #moderator commands and features (like ips)
- #better styling (bold, underline, even blinking and stuff!)
- #deal with nonexistant rooms
- #add command history with up arrow
- #disconnection error handling and stuff
- #dynamic sizing
- #secret messages to individual people instead of everyone as well
- #custom skins
- #config file or somethin
- #cursor movement with arrow keys
- #nick name aliases
- #special page for pms
- #fix bugs
- #fix self pages not appearing with echo on
- #saving/loading settings
- #OUTPUT MACROS! that would be epic
- #automated responses, and preprogrammed/saved messages, etc
- #away feature that tells people you are away if you are away and someone says yourname
- #log loading
- #name highlighting
- activeRoom = None
- generalBuffer = []
- contents = ["", "", ""]
- focus = 1
- raw = False
- disguise = "*hidden*"
- pages = {}
- echo = False
- screen = curses.initscr()
- curses.noecho()
- curses.start_color()
- HELP_MSG = "Press TAB to switch between the three text feilds at the bottom. Type in room names on the left to connect to rooms. Once connected, type in messages in the middle. Type in private messages on the right. To toggle between open rooms press \"`\". To toggle between private message recipients press \"~\". The selected recipient will be highlighted in red. Type /commands in the middle when connected to a room to get a list of commands that you can use. Press ESC twice to exit the client."
- size = screen.getmaxyx()
- WIDTH = int(size[1])
- HEIGHT = int(size[0])
- HALF = int(WIDTH / 2)
- QUARTER = int(WIDTH / 4)
- RED = 1
- YELLOW = 2
- GREEN = 3
- CYAN = 4
- BLUE = 5
- MAGENTA = 6
- NORMAL = 7
- SECRET = 8
- curses.init_pair(RED, curses.COLOR_RED, curses.COLOR_BLACK)
- curses.init_pair(YELLOW, curses.COLOR_YELLOW, curses.COLOR_BLACK)
- curses.init_pair(GREEN, curses.COLOR_GREEN, curses.COLOR_BLACK)
- curses.init_pair(CYAN, curses.COLOR_CYAN, curses.COLOR_BLACK)
- curses.init_pair(BLUE, curses.COLOR_BLUE, curses.COLOR_BLACK)
- curses.init_pair(MAGENTA, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
- curses.init_pair(NORMAL, curses.COLOR_WHITE, curses.COLOR_BLACK)
- curses.init_pair(SECRET, curses.COLOR_BLACK, curses.COLOR_WHITE)
- def printLine(string, color=NORMAL):
- generalBuffer.append((string, color))
- while len(generalBuffer) > HEIGHT - 3: del generalBuffer[0]
- def addMessage(string, color=NORMAL):
- for string in string.split("\n"):
- buf = ""
- for i in string:
- buf += i
- if len(buf) >= HALF:
- printLine(buf, color)
- buf = ""
- if buf != "": printLine(buf, color)
- if activeRoom == None:
- drawChat()
- screen.refresh()
- def hexToDec(h):
- h = h.lower()
- if h == "f":
- return 15
- elif h == "e":
- return 14
- elif h == "d":
- return 13
- elif h == "c":
- return 12
- elif h == "b":
- return 11
- elif h == "a":
- return 10
- else:
- try:
- return int(h)
- except:
- return 0
- def hexToAnsi(col):
- if col[0] == "#": col = col[1:]
- if len(col) == 3:
- r = hexToDec(col[0])
- g = hexToDec(col[1])
- b = hexToDec(col[2])
- elif len(col) == 6:
- r = hexToDec(col[0])
- g = hexToDec(col[2])
- b = hexToDec(col[4])
- else:
- return NORMAL
- r = r > 7
- g = g > 7
- b = b > 7
- if r and g and b:
- return NORMAL
- if r and g:
- return YELLOW
- if g and b:
- return CYAN
- if r and b:
- return MAGENTA
- if r:
- return RED
- if g:
- return GREEN
- if b:
- return BLUE
- else:
- return NORMAL
- def printLineR(r, string, color=NORMAL):
- r.chatBuffer.append((string, color))
- while len(r.chatBuffer) > HEIGHT - 3: del r.chatBuffer[0]
- def addMessageR(r, string, color=NORMAL):
- for string in string.split("\n"):
- buf = ""
- for i in string:
- buf += i
- if len(buf) >= HALF:
- printLineR(r, buf, color)
- buf = ""
- if buf != "": printLineR(r, buf, color)
- if activeRoom == r:
- drawChat()
- screen.refresh()
- else:
- r.new = True
- drawRooms()
- screen.refresh()
- def page(user, message):
- if user.lower() in pages.keys():
- pages[user.lower()].append(message)
- else:
- pages[user.lower()] = [message]
- def logMessage(message):
- f = open("log", "a")
- f.write(message.user.name + ": " + message.raw + "\n")
- f.close()
- def secretEncrypt(message, k):
- message = k + message
- message = base64.b64encode(message.encode('ascii'))
- insert = base64.b64encode(k.encode('ascii')*len(k))[:-2]
- location = random.randint(1, len(message))
- first = message[:location]
- if location == len(message):
- last = ""
- else:
- last = message[location:]
- message = first + insert + last
- message = base64.b64encode(message + k.encode('ascii'))
- return message
- def secretDecrypt(message, k):
- m = message
- try:
- message = base64.b64decode(bytes(message.split("'")[1], "ascii"))[:-len(k)]
- insert = base64.b64encode(k.encode('ascii')*len(k))[:-2]
- location = message.find(insert)
- first = message[:location]
- if location == len(message):
- last = ""
- else:
- last = message[location + len(insert):]
- message = first + last
- message = base64.b64decode(message)
- if message.startswith(k.encode('ascii')):
- return str(message)[len(k)+2:-1]
- else:
- raise Exception
- except Exception as e:
- return "ERROR DECRYPTING: " + m + " " + str(e)
- def checkSecret(room, user, message):
- if message.raw.find("<msg=\"") == -1: return False
- msg = message.raw.split("<msg=\"")[1]
- msg2 = ""
- p = None
- for char in msg:
- if char == "\"" and p != "\\": break
- if char == "\\":
- if p == "\\": msg2 += char
- else:
- msg2 += char
- p = char
- addMessageR(room, user.name + ": " + secretDecrypt(msg2, user.name.lower()), SECRET)
- return True
- class RoomManager(ch.RoomManager):
- def onHistoryMessage(self, room, user, message):
- checkSecret(room, user, message)
- addMessageR(room, user.name + ": " + message.body, hexToAnsi(message.fontColor))
- drawFields()
- screen.refresh()
- def onMessage(self, room, user, message):
- logMessage(message)
- if checkSecret(room, user, message) or not echo or user.name.lower() != NICK.lower(): addMessageR(room, user.name + ": " + message.body, hexToAnsi(message.fontColor))
- if user.name.lower() in pages.keys():
- for i in pages[user.name.lower()]: room.message("(PAGE) @" + user.name + ": " + i)
- del pages[user.name.lower()]
- curses.beep()
- drawFields()
- screen.refresh()
- def onPMMessage(self, pm, user, body):
- addMessageR(room, "@" + NICK + ": " + user.name + ": " + body, NORMAL)
- drawFields()
- screen.refresh()
- def onPMDisconnect(self, pm):
- addMessageR(room, "PM disconnect", NORMAL)
- drawFields()
- screen.refresh()
- def onLeave(self, room, user):
- addMessageR(room, user.name + " left the room.")
- drawOnline()
- drawFields()
- screen.refresh()
- def onJoin(self, room, user):
- addMessageR(room, user.name + " entered the room.")
- drawOnline()
- drawFields()
- screen.refresh()
- def onConnect(self, room):
- addMessageR(room, "You have connected to " + room.name + ".")
- drawFields()
- screen.refresh()
- def onReconnect(self, room):
- addMessageR(room, "You have reconnected to " + room.name + ".")
- drawFields()
- screen.refresh()
- def onFloodWarning(self, room):
- room.reconnect()
- def onUserCountChange(self, room):
- drawOnline()
- drawFields()
- screen.refresh()
- def centerString(string, length):
- off = length - len(string)
- if off > 0:
- l = int(off / 2)
- if off % 2:
- r = l + 1
- else:
- r = l
- return " " * l + string + " " * r
- elif off < 0:
- return string[:length]
- def leftString(string, length):
- if len(string) > length:
- return string[:length]
- else:
- off = length - len(string)
- return string + " " * off
- def rightString(string, length):
- if len(string) > length:
- return string[:length]
- else:
- off = length - len(string)
- return string + off * " "
- def fieldString(string, length):
- if len(string) > length:
- return string[-length:]
- else:
- off = length - len(string)
- return string + off * " "
- def fieldLength(string, length):
- if len(string) > length:
- return length
- else:
- return len(string)
- def drawRooms():
- y = 0
- for y in range(len(chatango.roomnames)):
- if y >= HEIGHT - 2: break
- room = list(chatango.roomnames)[y]
- if chatango.getRoom(room).new:
- attr = curses.color_pair(YELLOW) | curses.A_BOLD
- else:
- attr = curses.color_pair(NORMAL)
- if room == activeRoom.name: attr |= curses.A_REVERSE
- screen.addstr(y + 1, 0, leftString(room, QUARTER), attr)
- if len(chatango.roomnames) > 0: y += 1
- while y < HEIGHT - 2:
- screen.addstr(y + 1, 0, leftString("", QUARTER), curses.color_pair(NORMAL))
- y += 1
- def drawOnline():
- if not activeRoom:
- users = []
- else:
- users = sorted(activeRoom.usernames)
- y = 0
- for y in range(len(users)):
- if y >= HEIGHT - 2: break
- nick = users[y]
- if activeRoom and activeRoom.activeUser == users[y]:
- attr = curses.color_pair(RED) | curses.A_REVERSE
- else:
- attr = curses.color_pair(NORMAL)
- if nick == activeRoom.name: attr |= curses.A_REVERSE
- screen.addstr(y + 1, HALF + QUARTER, rightString(nick, QUARTER), attr)
- if len(users) > 0: y += 1
- while y < HEIGHT - 2:
- screen.addstr(y + 1, HALF + QUARTER, rightString("", QUARTER), curses.color_pair(NORMAL))
- y += 1
- def drawChat():
- if not activeRoom:
- chatBuffer = generalBuffer
- else:
- chatBuffer = activeRoom.chatBuffer
- y = 0
- for y in range(len(chatBuffer)):
- if y >= HEIGHT - 3: break
- message = chatBuffer[y]
- screen.addstr(y + 1, QUARTER, leftString(message[0], HALF), curses.color_pair(message[1]))
- if len(chatBuffer) > 0: y += 1
- while y < HEIGHT - 3:
- screen.addstr(y + 1, QUARTER, leftString("", HALF), curses.color_pair(NORMAL))
- y += 1
- def drawField(y, x, content, width, selected, color=NORMAL):
- attr = curses.color_pair(color)
- if selected: attr |= curses.A_REVERSE
- screen.addstr(y, x, fieldString(content, width), attr)
- if selected:
- return (y, x + fieldLength(content, width))
- else:
- return None
- def drawFields():
- if activeRoom:
- color = hexToAnsi(chatango.user.fontColor)
- else:
- color = NORMAL
- f1 = drawField(HEIGHT - 1, 0, contents[0], QUARTER - 1, focus == 0)
- f2 = drawField(HEIGHT - 1, QUARTER, contents[1], HALF - 1, focus == 1, color)
- f3 = drawField(HEIGHT - 1, HALF + QUARTER, contents[2], QUARTER - 1, focus == 2, color)
- if f1:
- screen.move(f1[0], f1[1])
- elif f2:
- screen.move(f2[0], f2[1])
- elif f3:
- screen.move(f3[0], f3[1])
- def drawHeaders():
- screen.addstr(0, 0, centerString("ROOMS", QUARTER), curses.color_pair(NORMAL) | curses.A_REVERSE)
- screen.addstr(0, QUARTER, centerString("CHATANGO", HALF), curses.color_pair(NORMAL) | curses.A_REVERSE)
- screen.addstr(0, HALF + QUARTER, centerString("ONLINE", QUARTER), curses.color_pair(NORMAL) | curses.A_REVERSE)
- def drawContent():
- drawRooms()
- drawOnline()
- drawChat()
- def drawInterface():
- drawHeaders()
- drawContent()
- drawFields()
- screen.refresh()
- def alreadyConnected(room):
- chatango.getRoom(room)
- def switchRoom(room):
- global activeRoom
- activeRoom = room
- room.new = False
- drawContent()
- drawHeaders()
- def hide(message, hidden):
- if hidden:
- return "*" * len(message)
- else:
- return message
- def dialogue(prompt, hidden=False):
- screen.border(0)
- screen.addstr(int(HEIGHT / 2), HALF - len(prompt), prompt)
- screen.refresh()
- RESULT = ""
- while True:
- c = screen.getch()
- if c == 10 and RESULT != "":
- RESULT = RESULT.strip()
- break
- elif c == 127:
- if RESULT != "": RESULT = RESULT[:-1]
- screen.addstr(int(HEIGHT / 2), HALF, leftString(hide(RESULT, hidden), HALF))
- screen.border(0)
- screen.move(int(HEIGHT / 2), HALF + len(RESULT))
- screen.refresh()
- elif c == 27:
- curses.endwin()
- sys.exit()
- else:
- if len(RESULT) < HALF - 1:
- RESULT += chr(c)
- if hidden:
- display = "*" * len(RESULT)
- else:
- display = RESULT
- screen.addstr(int(HEIGHT / 2), HALF, leftString(hide(RESULT, hidden), HALF))
- screen.border(0)
- screen.move(int(HEIGHT / 2), HALF + len(RESULT))
- screen.refresh()
- screen.erase()
- return RESULT
- NICK = dialogue("Enter your username: ")
- PASS = dialogue("Enter your password: ", True)
- chatango = RoomManager(NICK, PASS)
- chatango._userlistMode = ch.Userlist_All
- chatango_thread = threading.Thread(target=chatango.main,)
- chatango_thread.setDaemon(True)
- chatango_thread.start()
- drawInterface()
- addMessage("Welcome to MegaLoler's Chatango client!\npatch by ASL97 for python 3.x\nJoin a room to get started.")
- esc = False
- while True:
- drawFields() #perhaps make more effient later
- c = screen.getch()
- if c == 10: # Enter
- msg = contents[focus].strip()
- if msg != "":
- contents[focus] = ""
- if focus == 0:
- r = alreadyConnected(msg)
- if r == None:
- chatango.joinRoom(msg)
- room = chatango.getRoom(msg)
- room.chatBuffer = []
- room.activeUser = None
- room.new = False
- switchRoom(room)
- else:
- switchRoom(r)
- elif focus == 1:
- if activeRoom:
- if msg[0] == "/":
- msg = msg[1:]
- if msg != "":
- parts = msg.split(" ")
- cmd = parts[0].lower()
- if len(parts) == 1:
- pars = []
- else:
- pars = parts[1:]
- par = " ".join(pars)
- if cmd == "color":
- chatango.setFontColor(par)
- drawChat()
- elif cmd == "namecolor":
- chatango.setNameColor(par)
- drawOnline()
- elif cmd == "msg":
- activeRoom.message("<msg=\"" + str(secretEncrypt(par, NICK.lower())) + "\"></msg>" + disguise, True)
- elif cmd == "help":
- addMessageR(activeRoom, HELP_MSG)
- elif cmd == "page":
- if len(pars) > 1:
- page(pars[0], " ".join(pars[1:]))
- addMessageR(activeRoom, "The message will be sent when " + pars[0] + " is next seen.")
- else:
- addMessageR(activeRoom, "The syntax is /page [user] [message].")
- elif cmd == "pages":
- if par == "":
- if len(pages.keys()):
- addMessageR(activeRoom, "Pages are queued for the following users: " + ", ".join(pages.keys()) + ".")
- else:
- addMessageR(activeRoom, "No pages are queued.")
- else:
- if par.lower() in pages.keys():
- addMessageR(activeRoom, "The user " + par + " has the following pages queued:\n" + "\n".join(pages[par.lower()]))
- else:
- addMessageR(activeRoom, "The user " + par + " does not have any pages queued.")
- elif cmd == "disguise":
- disguise = par
- addMessageR(activeRoom, "Disguise set to \"" + disguise + "\".")
- elif cmd == "clear":
- activeRoom.chatBuffer = []
- addMessageR(activeRoom, "Cleared the screen.")
- elif cmd == "raw":
- raw = not raw
- if raw:
- addMessageR(activeRoom, "Raw mode is now ON.")
- else:
- addMessageR(activeRoom, "Raw mode is now OFF.")
- elif cmd == "echo":
- echo = not echo
- if echo:
- addMessageR(activeRoom, "Echo mode is now ON.")
- else:
- addMessageR(activeRoom, "Echo mode is now OFF.")
- elif cmd == "part":
- for i in range(len(chatango._rooms)):
- if list(chatango.roomnames)[i] == activeRoom.name: break
- chatango.leaveRoom(activeRoom.name)
- if len(chatango.roomnames) > 0:
- i %= len(chatango.roomnames)
- switchRoom(list(chatango.rooms)[i])
- else:
- activeRoom = None
- drawContent()
- else:
- addMessageR(activeRoom, "Commands: /color [color] (sets your font color), /namecolor [color] (sets your name color), /part (disconnect from the current room), /help (get help on how to use this client), /raw (toggles raw mode which allows you to type with html formatting), /clear (clears the screen), /msg [message] (sends a secret message that only special people can see, and the message will appear to others as defined by the /disguise command to obfuscate the fact that you are sending secret messages), /disguise [message] (sets the disguise message for use with the /msg command), /page [user] [message] (queue a message that will be sent when a certain user is seen), /pages (user) (lists any currently queued pages), /echo (toggles echo mode which echos user input immediately rather than waiting for it to echo from the server)")
- else:
- activeRoom.message(msg, raw)
- if echo: addMessageR(activeRoom, NICK + ": " + msg, hexToAnsi(chatango.user.fontColor))
- else:
- if msg == "/help":
- addMessage(HELP_MSG)
- else:
- addMessage("You are not connected to any rooms! Type /help for help.")
- elif focus == 2:
- if activeRoom and activeRoom.activeUser:
- chatango.pm.message(ch.User(activeRoom.activeUser), msg)
- addMessageR(activeRoom, "@" + activeRoom.activeUser + ": " + NICK + ": " + msg, hexToAnsi(chatango.user.fontColor))
- drawFields()
- screen.refresh()
- elif c == 9: # Tab
- focus = (focus + 1) % 3
- drawFields()
- screen.refresh()
- elif c == 96: # `
- if len(chatango.roomnames) > 0:
- for i in range(len(chatango.roomnames)):
- if list(chatango.roomnames)[i] == activeRoom.name: break
- i = (i + 1) % len(chatango.roomnames)
- switchRoom(list(chatango.rooms)[i])
- elif c == 126: # ~
- if activeRoom:
- if len(activeRoom.usernames) > 0:
- users = sorted(activeRoom.usernames)
- for i in range(len(users)):
- if users[i] == activeRoom.activeUser: break
- i = (i + 1) % (len(activeRoom.usernames))
- activeRoom.activeUser = users[i]
- drawOnline()
- elif c == 127: # Backspace
- if contents[focus] != "": contents[focus] = contents[focus][:-1]
- drawFields()
- screen.refresh()
- elif c == 27: # Esc
- if esc:
- break
- else:
- esc = True
- continue
- elif c == 35: # # \ 95 : _
- if activeRoom:
- try:
- msg = room._history[-1]
- if msg:
- room.rawClearUser(msg.unid)
- drawFields()
- screen.refresh()
- except:
- pass
- else:
- contents[focus] += chr(c)
- drawFields()
- screen.refresh()
- esc = False
- chatango.stop()
- curses.endwin()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement