Hello! This is my first post. I've put this up on several other forums, but this one seems slightly more active (python-forum.org is a snooze-fest). I have been learning python for a few months and decided to make a chat server that you connect to through telnet. It's working fine so far, but I just want to get feedback from people who know the language well. The code is no-doubt sloppy and I'm sure there are a TON of better and cleaner ways to do what I'm doing. I want all the criticism you have to offer ;).

PS. - I am deliberately not using Twisted. I want this to be as functional as possible using only the standard library.

#!/usr/bin/python


## Home Network Chat Server (HNCS) version 2.0
## Copyright (C) 2007  John M. Graff
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.



################################################
##          Imports, Constants
################################################
from time import *                              
from SocketServer import ThreadingTCPServer, BaseRequestHandler


endl            = '\r\n'
backspace       = '\b'
creturn         = '\r'
tab             = '\t'
space           = ' '

maxname         = 8
packet          = 1024                   

HOST            = ''                       
PORT            = 65438                    
ADDRESS         = (HOST, PORT)          

connections     = {}                
roomlist        = {}                   
#################################################


##################################################
##          Big Strings
##################################################
## This stuff is self-explanatory. Any one of these
## can be changed to whatever you want. The 'artwork'
## variable is fun to play with.

artwork = """          ,--.!,\r
       __/   -*-\r
     ,d08b.  '|`         HNCS version 2.0\r
     0088MM              (c)2007 J.Graff\r
     `9MMP'\r
"""

info = '''\r\n
---------------------------------------------------\r
                    INFO\r
---------------------------------------------------\r
Home Network Chat Server (HNCS) is an open source\r
and freely distributable chatserver written and\r
maintained by John M. Graff in the Python scripting\r
language ([url]http://www.python.org[/url]) and licensed under\r
the GPL (see [url]http://www.gnu.com[/url] for details).\r
\r\n
Source code is available upon request. E-mail the\r
author at [email]jmgraff@student.ysu.edu[/email] for more informa-\r
tion.\r
----------------------------------------------------\r\n'''

clientHelp = '''\r
-----------------------------------------------------\r
                  COMMANDS\r
-----------------------------------------------------\r\n
'c'          clear screen\r
'w'          print userlist\r
'i'          info about this program\r
'l'          list of rooms\r
'r'          list of people in current room\r
'q'          disconnect\r
'?'          show this menu\r
<Enter>      type & send a message\r\n

NOTE: Windows users only need to press \r
the key. UNIX/Linux/BSD users must press\r
<Enter> after the key.\r
-----------------------------------------------------\r'''

gpl = '''HNCS 2.0, Copyright (C) 2007 John M. Graff\r
HNCS comes with ABSOLUTELY NO WARRANTY; This is free\r
software,and you are welcome to redistribute it under\r
the terms and conditions of the GPL.\r\n
See [url]http://www.gnu.com[/url] for details.;\r'''

login = '''\r\nWelcome to the server!\r\n
Before you start, choose a Nickname.(Max 8 characters)\r
If it's too long or already in use, you'll have to try\r
again.\r\n
Nickname: '''
##################################################






###################################################
##          The HNCS Class
###################################################
class HNCS(ThreadingTCPServer):
    def verify_request(self, request, client_address):
        for key in connections:
            if connections[key].client_address[0] == client_address[0]:
                if client_address[0] != '127.0.0.1':
                    return False
        return True
    def welcome(self):
        return '''______________________________________________________
------------------------------------------------------
%s
______________________________________________________
------------------------------------------------------
* Server started %s
* Waiting for connections on port %i
''' % (gpl, ctime(), PORT)
##################################################




    
##################################################
##          The Room Class
##################################################
class Room:
    def __init__(self, name, starter):
        self.listeners = []
        self.name = name
        roomlist[self.name] = self
        self.listeners.append(starter)      
    def cleanup(self, connection):
        self.listeners.remove(connection)
        if len(self.listeners) == 0:
            del roomlist[self.name]   
    def whoHere(self):
        container = ''
        for listener in self.listeners:
            container += listener.__str__()
        format =  '[ %s ]' % (container)
        return endl + format + endl
    def listRooms(self):
        container = ''
        for key in roomlist:
            container += (roomlist[key].__str__() + endl)
        format = '''\r
-----------------------------------------------------\r
                  ROOMS\r
-----------------------------------------------------\r\n
%s\r
-----------------------------------------------------\r'''% container
        return format
    def welcome(self):
        return endl + '\t*  You are now in :: %s ::  *\r\n %s\r\n' % (self.name, self.whoHere())
    def __str__(self):
        return ':: %s ::\t\t|\t\t%i user(s)' % (self.name, len(self.listeners))

        
        
###################################################
##          The Connection Class
###################################################
# This is where it all goes down. Upon connection,
# this class automatically runs the self.setup()
# function which sets up a self.username and appends
# a unique instance of the User class to the 'connec-
# tions' list. self.getPress is an infinite loop and
# is where the majority of the action takes place.

class Connection(BaseRequestHandler):    
    def setup(self):
        print '[!] %s %s' % (self.client_address[0], ctime())
        self.room = 0
        self.login()
        self.joinRoom('lobby')
        print '- %s logged in as "%s"' % (self.client_address[0], self.name)
        self.broadcast('\r\n\t*  %s connected %s  *\r\n' % (self, ctime()))
        connections[self.name] = self
        self.request.send('You are now logged in as "%s".\r\n' % self.name)        
    def login(self):
        self.request.send(login)
        string = self.getString()
        while len(string) > maxname or connections.has_key(string):
            self.request.send('\r\nSorry, try again: ')
            string = self.getString()
        if len(string) < maxname:
            string = (space * (maxname - len(string))) + string
        self.name = string       
    def handle(self):
        sleep(2)
        self.request.send(self.welcome())           # send the welcome note
        self.getPress()                             # wait for commands until session is terminated        
    def finish(self):
        self.broadcast('\r\n\t*  %s has disconnected  *\r\n' % self)
        print '[x] %s %s (%s)' % (self.client_address, self.name, ctime(), )
        del connections[self.name]
        self.leaveRoom()
        self.request.close()
    def getPress(self):
        while True:
            try:
                press = self.request.recv(packet)
                self.request.send('\b ')                    # keep the cursor from moving
                self.request.send('\b')                     #           "
                if press in ('?', '?\r\n'):                 # print the help menu
                    self.request.send(clientHelp)
                elif press in ('w', 'w\r\n'):               # print who is online
                    self.request.send(self.who())
                elif press in ('r', 'r\r\n'):               # print users in room
                    self.request.send(self.room.whoHere())
                elif press == endl:                         # go to the message prompt
                    self.message()
                elif press in ('c', 'c\r\n'):               # clear the screen (kind of a primative way, eh?)
                    self.request.send(endl * 100)
                elif press in ('i', 'i\r\n'):               # print the info string
                    self.request.send(info)
                elif press in ('j', 'j\r\n'):
                    self.changeRoom()
                elif press in ('l', 'l\r\n'):
                    self.request.send(self.room.listRooms())
                elif press in ('q', 'q\r\n'):               # leave the loop and terminate the session
                    break
                else:
                    self.request.send('\r\n\t*  Dude, no. Type ? for'
                                      ' a list of commands.  *\r\n')
            except: 
                break                                                                           
    def getString(self):
        string      = ''
        keypress    = []
        press       = self.request.recv(packet)                 # get a keypress
        if len(press) <= 1:                                     # IF CLIENT IS IN CHAR MODE... (WINDOWS)
            while press != endl:                                    # while user isn't finished,
                if press == '\b':                                       # if it was a backspace,
                    if (len(keypress) > 0):                                     # and the keypress array isn't empty,
                        self.request.send(' \b')                                    # send a coverup so it looks like a backspace
                        keypress.pop()                                              # and remove last char
                    else:                                                       # if array is empty
                        while press == '\b':                                        # while still pressing backspace
                            self.request.send(' ')                                      # send blank
                            press = self.request.recv(packet)                           # get and test a new press
                        if press == endl: break
                        keypress.append(press)
                else:
                    keypress.append(press)                            # if it wasn't a backspace, append it to array
                press = self.request.recv(packet)                       # get a new press
            for key in keypress:                                    # for each key held in keypress
                string += key                                           # add it to the string
                
        else:                                               # IF THE CLIENT IS IN LINE MODE (*NIX), 
            string = press                                      # the press is the string.
        if endl in string:                                  # if an end-of-line is in the string,
            string = string.strip(endl)                         # remove it.
        return string                                       # return the string
    def message(self):
        self.request.send('> %s: ' % self.name)
        string = self.getString()
        if len(string) == 0:
            self.request.send('\t<no message sent>\r\n')
        else:
            msg = '\r\n> %s: %s\r\n' % (self.name, string)
            self.roomcast(msg)                        
    def broadcast(self, data):
        for key in connections:
            if connections[key] != self:
                connections[key].request.send(data)
    def roomcast(self, data):
        for listener in self.room.listeners:
            if listener != self:
                listener.request.send(data)
    def leaveRoom(self):
        if self.room:
            self.room.cleanup(self)
    def changeRoom(self):
        self.request.send('\r\njoin room: ')
        string = self.getString()
        if len(string) == 0:
               self.request.send('\t<aborted>\r\n')
        else:
               self.joinRoom(string)              
    def joinRoom(self, roomname):
        if self.room:                                                                  
            if self.room.name == roomname:                                                   
                self.request.send('\r\n\t*  You are already in ::%s::  *\r\n' % self.room.name)        
            else:
                self.room.cleanup(self)
                if roomlist.has_key(roomname):
                    self.room = roomlist[roomname]
                    self.room.listeners.append(self)
                    self.request.send(self.room.welcome())
                else:
                    self.room = Room(roomname, self)
                    self.request.send(self.room.welcome())
        else:                                                                          
            if roomlist.has_key(roomname):
                self.room = roomlist[roomname]
                self.room.listeners.append(self)
            else:
                self.room = Room(roomname, self)     
    def who(self):
        container = ''
        for user in connections:
            container += user.__str__()
        format = '[%s]' % container
        return endl + format + endl
    def welcome(self):
        return '''\r\n
%s
_______________________________________________________\r
-------------------------------------------------------\r
%s\r
\t\tType '?' for help.\r
_______________________________________________________\r
-------------------------------------------------------\r
Local time is %s\r
%s
''' % (gpl, artwork, ctime(), self.room.welcome())

                                
    def __str__(self):
        return ' %s ' % self.name
###################################################

server = HNCS(ADDRESS, Connection)

if __name__ == '__main__':
    print server.welcome()
    server.serve_forever()

Interesting project! I am working myself though the code. I have never written anyhing like that.

Oh! This is interesting.
I've just ran the code and connected a couple of clients.
This is actually good.

To be honest I would have a functional use for this. Not only am I learning python myself but I was also looking for an ultra-simple chat program to run on my network. My network incorporates a lot of different OS and some systems don't have GUIs, or other 'standard' features like that and I was looking for a chat program that can run on all systems with as little effort as possible,

I think I can learn from this as well as have a functional use. Glad I read this post!
I hope you don't mind if I have a play around with this and see what I can make it do =]

This looks pretty cool.
I must admit that I'm a noob when it comes to Python programming, but, I'm just wondering how I connect to the server, because when I run the python file it just says: Waiting for connections on port 65438.

How do I connect and start writing ?

Have played around with some things and here is one which was in my to-do-list
Great! I will see it when I finish current exercise
Thanks sir!!

telnet ip.of.the.server port

Just telnet into it :)

I think they figured it out after 3 years lol.

well I'm just finding this code today and it had still not been answered so it'll help future people if they happen across it as well. :P

And just wanted to add that it works great! Pretty good after only a month's worth of learning!

The only question I have is how do you create a room? I see the option's just haven't figured it out yet.

*edit* found it just type j... awesome stuff!

Edited 5 Years Ago by danstinebaugh: addition

This article has been dead for over six months. Start a new discussion instead.