Advertisement
MegaLoler

Tinychat Terminal Interface Client

Apr 29th, 2012
995
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.79 KB | None | 0 0
  1. import tinychat
  2. import curses
  3. import thread
  4. import sys
  5.  
  6. # Tinychat client by MegaLoler
  7.  
  8. rooms = []
  9. activeRoom = None
  10. generalBuffer = []
  11.  
  12. contents = ["", "", ""]
  13. focus = 1
  14.  
  15. screen = curses.initscr()
  16. curses.noecho()
  17. curses.start_color()
  18.  
  19. 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."
  20.  
  21. size = screen.getmaxyx()
  22. WIDTH = size[1]
  23. HEIGHT = size[0]
  24. HALF = WIDTH / 2
  25. QUARTER = WIDTH / 4
  26.  
  27. RED = 1
  28. YELLOW = 2
  29. GREEN = 3
  30. CYAN = 4
  31. BLUE = 5
  32. MAGENTA = 6
  33. NORMAL = 7
  34.  
  35. curses.init_pair(RED, curses.COLOR_RED, curses.COLOR_BLACK)
  36. curses.init_pair(YELLOW, curses.COLOR_YELLOW, curses.COLOR_BLACK)
  37. curses.init_pair(GREEN, curses.COLOR_GREEN, curses.COLOR_BLACK)
  38. curses.init_pair(CYAN, curses.COLOR_CYAN, curses.COLOR_BLACK)
  39. curses.init_pair(BLUE, curses.COLOR_BLUE, curses.COLOR_BLACK)
  40. curses.init_pair(MAGENTA, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
  41. curses.init_pair(NORMAL, curses.COLOR_WHITE, curses.COLOR_BLACK)
  42.  
  43. def printLine(string, color=NORMAL):
  44.     generalBuffer.append((string, color))
  45.     while len(generalBuffer) > HEIGHT - 3: del generalBuffer[0]
  46.  
  47. def addMessage(string, color=NORMAL):
  48.     for string in string.split("\n"):
  49.         buf = ""
  50.         for i in string:
  51.             buf += i
  52.             if len(buf) >= HALF:
  53.                 printLine(buf, color)
  54.                 buf = ""
  55.         if buf != "": printLine(buf, color)
  56.     if activeRoom == None:
  57.         drawChat()
  58.         screen.refresh()
  59.  
  60. def hexToDec(h):
  61.     h = h.lower()
  62.     if h == "f":
  63.         return 15
  64.     elif h == "e":
  65.         return 14
  66.     elif h == "d":
  67.         return 13
  68.     elif h == "c":
  69.         return 12
  70.     elif h == "b":
  71.         return 11
  72.     elif h == "a":
  73.         return 10
  74.     else:
  75.         try:
  76.             return int(h)
  77.         except:
  78.             return 0
  79.  
  80. def hexToAnsi(col):
  81.     if col[0] == "#": col = col[1:]
  82.     if len(col) == 3:
  83.         r = hexToDec(col[0])
  84.         g = hexToDec(col[1])
  85.         b = hexToDec(col[2])
  86.     elif len(col) == 6:
  87.         r = hexToDec(col[0])
  88.         g = hexToDec(col[2])
  89.         b = hexToDec(col[4])
  90.     else:
  91.         return NORMAL
  92.  
  93.     r = r > 3
  94.     g = g > 3
  95.     b = b > 3
  96.  
  97.     if r and g and b:
  98.         return NORMAL
  99.     if r and g:
  100.         return YELLOW
  101.     if g and b:
  102.         return CYAN
  103.     if r and b:
  104.         return MAGENTA
  105.     if r:
  106.         return RED
  107.     if g:
  108.         return GREEN
  109.     if b:
  110.         return BLUE
  111.     else:
  112.         return NORMAL
  113.  
  114. class TinychatRoom(tinychat.TinychatRoom):
  115.     def printLine(self, string, color=NORMAL):
  116.         self.chatBuffer.append((string, color))
  117.         while len(self.chatBuffer) > HEIGHT - 3: del self.chatBuffer[0]
  118.    
  119.     def addMessage(self, string, color=NORMAL):
  120.         for string in string.split("\n"):
  121.             buf = ""
  122.             for i in string:
  123.                 buf += i
  124.                 if len(buf) >= HALF:
  125.                     self.printLine(buf, color)
  126.                     buf = ""
  127.             if buf != "": self.printLine(buf, color)
  128.         if activeRoom == self:
  129.             drawChat()
  130.             screen.refresh()
  131.         else:
  132.             self.new = True
  133.             drawRooms()
  134.             screen.refresh()
  135.            
  136.     def onMessage(self, user, message):
  137.         self.addMessage(user.nick + ": " + message.msg, hexToAnsi(message.color))
  138.    
  139.     def onPM(self, user, message):
  140.         self.addMessage("@" + self.nick + ": " + user.nick + ": " + message.msg, hexToAnsi(message.color))
  141.    
  142.     def onQuit(self, user):
  143.         self.addMessage(user.nick + " left the room.")
  144.         drawOnline()
  145.         screen.refresh()
  146.    
  147.     def onJoin(self, user):
  148.         self.addMessage(user.nick + " entered the room.")
  149.         drawOnline()
  150.         screen.refresh()
  151.    
  152.     def onRegister(self, user):
  153.         self.addMessage("You have connected to " + self.room + ".")
  154.    
  155.     def onNickChange(self, new, old, user):
  156.         self.addMessage(old + " changed nickname to " + new + ".")
  157.         drawOnline()
  158.         screen.refresh()
  159.    
  160.     def onUserList(self, users):
  161.         drawOnline()
  162.         screen.refresh()
  163.        
  164.     def onTopic(self, topic):
  165.         self.addMessage("Topic set to \"" + topic + "\"")
  166.         drawHeaders()
  167.  
  168. def centerString(string, length):
  169.     off = length - len(string)
  170.     if off > 0:
  171.         l = off / 2
  172.         if off % 2:
  173.             r = l + 1
  174.         else:
  175.             r = l
  176.         return " " * l + string + " " * r
  177.     elif off < 0:
  178.         return string[:length]
  179.  
  180. def leftString(string, length):
  181.     if len(string) > length:
  182.         return string[:length]
  183.     else:
  184.         off = length - len(string)
  185.         return string  + " " * off
  186.  
  187. def rightString(string, length):
  188.     if len(string) > length:
  189.         return string[:length]
  190.     else:
  191.         off = length - len(string)
  192.         return string + off * " "
  193.  
  194. def fieldString(string, length):
  195.     if len(string) > length:
  196.         return string[-length:]
  197.     else:
  198.         off = length - len(string)
  199.         return string + off * " "
  200.  
  201. def fieldLength(string, length):
  202.     if len(string) > length:
  203.         return length
  204.     else:
  205.         return len(string)
  206.  
  207. def drawRooms():
  208.     y = 0
  209.     for y in range(len(rooms)):
  210.         if y >= HEIGHT - 2: break
  211.         room = rooms[y]
  212.         if room.new:
  213.             attr = curses.color_pair(YELLOW) | curses.A_BOLD
  214.         else:
  215.             attr = curses.color_pair(NORMAL)
  216.         if room == activeRoom: attr |= curses.A_REVERSE
  217.         screen.addstr(y + 1, 0, leftString(room.room, QUARTER), attr)
  218.     if len(rooms) > 0: y += 1
  219.     while y < HEIGHT - 2:
  220.         screen.addstr(y + 1, 0, leftString("", QUARTER), curses.color_pair(NORMAL))
  221.         y += 1
  222.  
  223. def drawOnline():
  224.     if not activeRoom:
  225.         users = []
  226.     else:
  227.         users = sorted(activeRoom.users.items())
  228.     y = 0
  229.     for y in range(len(users)):
  230.         if y >= HEIGHT - 2: break
  231.         nick = users[y][1].nick
  232.         if activeRoom and activeRoom.activeUser == users[y][1]:
  233.             attr = curses.color_pair(RED) | curses.A_REVERSE
  234.         else:
  235.             attr = curses.color_pair(NORMAL)
  236.         if nick == activeRoom.nick: attr |= curses.A_REVERSE
  237.         screen.addstr(y + 1, HALF + QUARTER, rightString(nick, QUARTER), attr)
  238.     if len(users) > 0: y += 1
  239.     while y < HEIGHT - 2:
  240.         screen.addstr(y + 1, HALF + QUARTER, rightString("", QUARTER), curses.color_pair(NORMAL))
  241.         y += 1
  242.  
  243. def drawChat():
  244.     if not activeRoom:
  245.         chatBuffer = generalBuffer
  246.     else:
  247.         chatBuffer = activeRoom.chatBuffer
  248.     y = 0
  249.     for y in range(len(chatBuffer)):
  250.         if y >= HEIGHT - 3: break
  251.         message = chatBuffer[y]
  252.         screen.addstr(y + 1, QUARTER, leftString(message[0], HALF), curses.color_pair(message[1]))
  253.     if len(chatBuffer) > 0: y += 1
  254.     while y < HEIGHT - 3:
  255.         screen.addstr(y + 1, QUARTER, leftString("", HALF), curses.color_pair(NORMAL))
  256.         y += 1
  257.  
  258. def drawField(y, x, content, width, selected, color=NORMAL):
  259.     attr = curses.color_pair(color)
  260.     if selected: attr |= curses.A_REVERSE
  261.     screen.addstr(y, x, fieldString(content, width), attr)
  262.     if selected:
  263.         return (y, x + fieldLength(content, width))
  264.     else:
  265.         return None
  266.  
  267. def drawFields():
  268.     if activeRoom:
  269.         color = hexToAnsi(activeRoom.color)
  270.     else:
  271.         color = NORMAL
  272.     f1 = drawField(HEIGHT - 1, 0, contents[0], QUARTER - 1, focus == 0)
  273.     f2 = drawField(HEIGHT - 1, QUARTER, contents[1], HALF - 1, focus == 1, color)
  274.     f3 = drawField(HEIGHT - 1, HALF + QUARTER, contents[2], QUARTER - 1, focus == 2, color)
  275.     if f1:
  276.         screen.move(f1[0], f1[1])
  277.     elif f2:
  278.         screen.move(f2[0], f2[1])
  279.     elif f3:
  280.         screen.move(f3[0], f3[1])
  281.        
  282. def drawHeaders():
  283.     if activeRoom and activeRoom.topic:
  284.         header = "TOPIC: " + activeRoom.topic
  285.     else:
  286.         header = "TINYCHAT"
  287.     screen.addstr(0, 0, centerString("ROOMS", QUARTER), curses.color_pair(NORMAL) | curses.A_REVERSE)
  288.     screen.addstr(0, QUARTER, centerString(header, HALF), curses.color_pair(NORMAL) | curses.A_REVERSE)
  289.     screen.addstr(0, HALF + QUARTER, centerString("ONLINE", QUARTER), curses.color_pair(NORMAL) | curses.A_REVERSE)
  290.  
  291. def drawContent():
  292.     drawRooms()
  293.     drawOnline()
  294.     drawChat()
  295.  
  296. def drawInterface():
  297.     drawHeaders()
  298.     drawContent()
  299.     drawFields()
  300.     screen.refresh()
  301.  
  302. def alreadyConnected(room):
  303.     for i in rooms:
  304.         if i.room.lower() == room.lower(): return i
  305.     return None
  306.    
  307. def switchRoom(room):
  308.     global activeRoom
  309.     activeRoom = room
  310.     room.new = False
  311.     drawContent()
  312.     drawHeaders()
  313.  
  314. screen.border(0)
  315. prompt = "Enter your username: "
  316. screen.addstr(HEIGHT / 2, HALF - len(prompt), prompt)
  317. screen.refresh()
  318.  
  319. NICK = ""
  320. while True:
  321.     c = screen.getch()
  322.     if c == 10 and NICK != "":
  323.         NICK = NICK.strip()
  324.         break
  325.     elif c == 127:
  326.         if NICK != "": NICK = NICK[:-1]
  327.         screen.addstr(HEIGHT / 2, HALF, leftString(NICK, HALF))
  328.         screen.border(0)
  329.         screen.move(HEIGHT / 2, HALF + len(NICK))
  330.         screen.refresh()
  331.     elif c == 27:
  332.         curses.endwin()
  333.         sys.exit()
  334.     else:
  335.         if len(NICK) < HALF - 1:
  336.             NICK += chr(c)
  337.             screen.addstr(HEIGHT / 2, HALF, leftString(NICK, HALF))
  338.             screen.border(0)
  339.             screen.move(HEIGHT / 2, HALF + len(NICK))
  340.             screen.refresh()
  341.  
  342. screen.erase()
  343.  
  344. drawInterface()
  345.  
  346. addMessage("Welcome to MegaLoler's Tinychat Client!\nJoin a room to get started.")
  347.  
  348. #todo
  349. #moderator commands
  350. #deal with nonexistant rooms
  351. #add command history with up arrow
  352. #disconnection error handling and stuff
  353. #dynamic sizing
  354.  
  355. esc = False
  356. while True:
  357.     drawFields() #perhaps make more effient later
  358.     c = screen.getch()
  359.     if c == 10: # Enter
  360.         msg = contents[focus].strip()
  361.         if msg != "":
  362.             contents[focus] = ""
  363.             if focus == 0:
  364.                 r = alreadyConnected(msg)
  365.                 if r == None:
  366.                     room = TinychatRoom(msg, NICK)
  367.                     thread.start_new_thread(room.connect, ())
  368.                     room.chatBuffer = []
  369.                     room.activeUser = None
  370.                     room.new = False
  371.                     rooms.append(room)
  372.                     switchRoom(room)
  373.                 else:
  374.                     switchRoom(r)
  375.             elif focus == 1:
  376.                 if activeRoom:
  377.                     if msg[0] == "/":
  378.                         msg = msg[1:]
  379.                         if msg != "":
  380.                             parts = msg.split(" ")
  381.                             cmd = parts[0].lower()
  382.                             if len(parts) == 1:
  383.                                 pars = []
  384.                             else:
  385.                                 pars = parts[1:]
  386.                             par = " ".join(pars)
  387.                             if cmd == "color":
  388.                                 activeRoom.cycleColor()
  389.                                 drawChat()
  390.                             elif cmd == "nick":
  391.                                 activeRoom.setNick(par)
  392.                                 drawOnline()
  393.                             elif cmd == "help":
  394.                                 activeRoom.addMessage(HELP_MSG)
  395.                             elif cmd == "part":
  396.                                 for i in range(len(rooms)):
  397.                                     if rooms[i] == activeRoom: break
  398.                                 rooms.remove(activeRoom)
  399.                                 activeRoom.disconnect()
  400.                                 if len(rooms) > 0:
  401.                                     i %= len(rooms)
  402.                                     switchRoom(rooms[i])
  403.                                 else:
  404.                                     activeRoom = None
  405.                                     drawContent()
  406.                                
  407.                             else:
  408.                                 activeRoom.addMessage("Commands: /color (cycles through a list of possible colors), /nick [nick] (allows you to change your nick), /part (disconnect from the current room), /help (get help on how to use this client)")
  409.                     else:
  410.                         activeRoom.say(msg)
  411.                         activeRoom.addMessage(activeRoom.nick + ": " + msg, hexToAnsi(activeRoom.color))
  412.                 else:
  413.                     if msg == "/help":
  414.                         addMessage(HELP_MSG)
  415.                     else:
  416.                         addMessage("You are not connected to any rooms!  Type /help for help.")
  417.             elif focus == 2:
  418.                 if activeRoom and activeRoom.activeUser:
  419.                     activeRoom.pm(msg, activeRoom.activeUser.nick)
  420.                     activeRoom.addMessage("@" + activeRoom.activeUser.nick + ": " + activeRoom.nick + ": " + msg, hexToAnsi(activeRoom.color))
  421.             drawFields()
  422.             screen.refresh()
  423.     elif c == 9: # Tab
  424.         focus = (focus + 1) % 3
  425.         drawFields()
  426.         screen.refresh()
  427.     elif c == 96: # `
  428.         if len(rooms) > 0:
  429.             for i in range(len(rooms)):
  430.                 if rooms[i] == activeRoom: break
  431.             i = (i + 1) % len(rooms)
  432.             switchRoom(rooms[i])
  433.     elif c == 126: # ~
  434.         if activeRoom:
  435.             if len(activeRoom.users) > 0:
  436.                 users = sorted(activeRoom.users.items())
  437.                 for i in range(len(users)):
  438.                     if users[i][1] == activeRoom.activeUser: break
  439.                 i = (i + 1) % (len(activeRoom.users))
  440.                 activeRoom.activeUser = users[i][1]
  441.                 drawOnline()
  442.     elif c == 127: # Backspace
  443.         if contents[focus] != "": contents[focus] = contents[focus][:-1]
  444.         drawFields()
  445.         screen.refresh()
  446.     elif c == 27: # Esc
  447.         if esc:
  448.             break
  449.         else:
  450.             esc = True
  451.             continue
  452.     else:
  453.         contents[focus] += chr(c)
  454.         drawFields()
  455.         screen.refresh()
  456.     esc = False
  457.  
  458. curses.endwin()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement