CS21 Lab 11: Swatcord (objects)
Due Wednesday, December 10, before midnight
Please read through the entire lab before starting!
In this lab, you’ll write classes to represent Message and
MessageCollection objects that you’ll use to implement a text-based chat
program (Swatcord, a simplified, text-version of Discord). With some help
from a few instructor-provided functions, your program will connect to a server
and send messages over the network to exchange chat messages with other users.
All of the standard style guidelines from previous labs still apply. Be sure to follow good top-down-design practices.
|
For this lab, you will be exchanging messages with other people (your classmates and instructors). Please be mindful of what you say and keep the discussion respectful and appropriate for all audiences. For the purposes of moderation, the messages you send are not anonymous. We are logging enough details about every message to track people down if need be. |
As you write programs, use good programming practices:
-
Use a comment at the top of the file to describe the purpose of the program (see example).
-
All programs should have a
main()function (see example). -
Use variable names that describe the contents of the variables.
-
Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.
-
Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.
-
Avoid writing any lines of code that exceed 80 columns.
-
Always work in a terminal window that is 80 characters wide (resize it to be this wide)
-
In
vscode, at the bottom right in the window, there is an indication of both the line and the column of the cursor.
-
Function Comments
All functions should include a comment explaining their purpose, parameters, return value, and describe any side effects. Please see our function example page if you are confused about writing function comments.
Goals
The goals for this lab assignment are:
-
Gain experience writing classes.
-
Gain experience reading and using classes and provided library functions.
Demo
For this lab, the behavior is best demonstrated as a video:
Note that you must be logged in via your @swarthmore.edu Google account to
access this recording.
1. Writing Classes
For this lab, you’ll be doing your work in three files:
-
message.py: This file will define aMessageclass that you’ll use to represent the information you know about one message (i.e., the sender’s name, a timestamp of when it was sent, and the body of the message). -
messagecollection.py: This file will define aMessageCollectionclass that you’ll use to represent a collection of orderedMessageobjects. It will also provide methods for printing information about the collection as a whole. -
swatcord.py: The main file that will contain yourmainfunction and supporting helper functions. The code in this file will "put everything together" by creating objects and calling instructor-provided helper functions.
1.1. Testing
-
As always, you should test your program as you go.
-
Since you’re working with three files, it’s a good idea to put the tests for your class in the corresponding file.
-
Like we saw in lecture, this can be done by writing a
main()method, and then calling it at the bottom using:if __name__ == "__main__": main()This way, your testing code will run when you execute
python3 message.pyon the command line, but it won’t run when youimportfrommessageinswatcord.py
1.2. Timestamps
This lab uses "timestamp" values in several places to record when an event happened (e.g., when a message was delivered to the server). Python stores timestamps as a floating point value that represents the number of seconds since the Unix Epoch (January 1, 1970).
When testing messages below, you can use the time() function to generate a
timestamp for the current time. If you print the timestamp, you’ll see a large
number. To make it human-readable, we’ve provided a library called
swatcord_library_time that has a function named timestamp_to_string. This
helper function will convert a timestamp float into a human-readable date and
time string. For example:
from time import time
currenttime = time()
print(currenttime)
print(timestamp_to_string(currenttime))
The block of code above would print output that looks like:
1764090153.2244332 Nov 25 12:02
For reference / testing purposes, here are a few timestamps and their human-readable conversions:
1763140849.8367949 Nov 14 12:20 1764340849.8367949 Nov 28 09:40 1764900849.8367949 Dec 04 21:14 1765340849.8367949 Dec 09 23:27
1.3. The Message Class
The message.py file should contain a class definition for a class named
Message. The Message class should have at minimum the following methods:
-
Constructor (
__init(…)__)-
The constructor should take two string parameters: a
nicknamestring, and abodystring. It should save these variables as instance variables for the new object. -
The constructor should also define a
timestampinstance variable (float) whose initial value is0.0.
-
-
get_nickname(…)-
This method should take no parameters (other than
self), and it should return the nickname string variable that is stored with the object.
-
-
get_body(…)-
This method should take no parameters (other than
self), and it should return the body string variable that is stored with the object.
-
-
set_timestamp(…)-
This method should take one parameter (in addition to
self): the timestamp (a floating point value) to save with the object. It does not return any value.
-
-
get_timestamp(…)-
This method should take no parameters (other than
self), and it should return the timestamp float variable that is stored with the object.
-
-
__str__(…)-
This method should take no parameters (other than
self), and it should return a string with a human-readable version of theMessagethat is suitable for printing. -
This method should not print anything, it just returns a string.
-
Note that you will not call this method directly. Python will automatically call it when you attempt to convert a
Messageto a string (e.g., when you callstr()on an instance of the object or pass it toprint). -
You can use the provided
timestamp_to_stringfunction to convert a floating point timestamp value into a human-readable date and time. It accepts a timestamp float and returns a string. -
For example, given a message that contains
'kwebb',1763140849.8367949, and'This is a test message!'in the nickname, timestamp, and body fields, you might generate and return a string that looks like:[Nov 14 12:20] kwebb: This is a test message!
-
|
Before moving on to the |
1.4. The MessageCollection Class
The messagecollection.py file should contain a class definition for a class
named MessageCollection. Objects of this class will be sent back to your
program by the server when you request retrieval of messages.
The MessageCollection class should have at minimum the following
methods:
-
Constructor (
__init(…)__)-
The constructor should take one parameter (other than
self), the name of a channel (string). It should save the channel name as an instance variable and also initialize an empty list instance variable that will later store a list ofMessageobjects.
-
-
add_message(…)-
This method should take one parameter (in addition to
self): theMessageobject to add to the collection. It should add the message to the end of the object’s stored message list. It does not return any value.
-
-
get_messages(…)-
This method should take no parameters (other than
self), and it should return the stored list ofMessageobjects.
-
-
__str__()-
This method should take no parameters (other than
self), and it should return a string with the string representation of eachMessagein the collection on a separate line (separated by newline characters\n). -
This method should not print anything, it just returns a string.
-
Note that you will not call this method directly. Python will automatically call it when you attempt to convert a
MessageCollectionto a string (e.g., when you callstr()on the instance object or you pass it toprint).
-
-
__len__()-
This method should take no parameters (other than
self), and it should return an integer representing the number ofMessageobjects that are stored in thisMessageCollection. -
Note that you will not call this method directly. Python will automatically call it when you call
len()on aMessageCollectionobject.
-
|
Before moving on, add tests cases to a |
Here’s an example of what it might look like to print a MessageCollection
that has three Message objects added to it:
[Nov 14 12:20] kwebb: First message for the general channel! [Nov 15 12:48] kwebb: Test message? [Nov 15 12:49] kwebb: Yay, it's working
2. Provided Library
This lab provides a swatcord_library library that gives you several helpful
functions to handle sending and receiving messages over the network. To use
the functions described below, you will import them from swatcord_library.
# Note: This is already done for you in the starter code for swatcord.py
from swatcord_library import *
Here are the functions included in swatcord_library that you’ll need to use:
-
connect_to_server()def connect_to_server(): """ This function takes no argument and returns a connection object. Parameters: None Returns: A connection object that you can pass to other library functions. """-
This function takes no argument and returns a connection object (known as a socket) to you. Other than calling
.close()on this connection object when you’re done with it, you won’t call any methods on it directly. You’ll pass it around to the other functions below.connection = connect_to_server()
-
-
channel_list(connection)def channel_list(connection): """ This function uses the provided connection to retrieve a comma-separated string containing channel names from the server. Parameters: connection: An active connection to the server, used to retrieve the results. Returns: A string containing the names of the available chat channels. The channel names are separated by commas. """-
This function takes one argument: a connection object. It will return a string containing the names of the server’s chat channels, separated by commas.
-
-
send_message(connection, channel, message)def send_message(connection, channel, message): """ Sends a message to the server. Parameters: connection: An active connection to the server. channel: A string containing the name of the channel to send the message to. You need to validate that the channel string exists before attempting to send to it. message: A message object containing the message to send. Returns: Nothing """-
This function takes three arguments: a connection object, a channel string, and a
Messageobject. It will send the contents of the message to the specified channel. -
Note that when you’re creating a
Messageto send with this function, you can leave the timestamp as the initial 0.0 value. It’s the server’s responsibility to assign a timestamp to each message.
-
-
receive_messages(connection, channel, timestamp)def receive_messages(connection, channel, timestamp): """ Receives a collection of messages from the server. Parameters: connection: An active connection to the server. channel: A string containing the name of the channel to receive the messages from. You need to validate that the channel string exists before attempting to receive from it. timestamp: A timestamp float indicating that you only want messages that arrived at the server after this time. Returns: A MessageCollection object that contains one or more Message objects. """-
This function takes three arguments: a connection object, a channel string, and a timestamp float. It will return to you a
MessageCollectionobject containing at most 20 messages (but possibly fewer than 20 messages) that have been sent to the specified channel since the provided timestamp. That is, it will only retrieve messages that came after the timestamp you provide. -
If you’re calling this function for the first time (e.g., a user just requested to start retrieving messages from a channel), you can set the timestamp to 0.0 to retrieve the 20 most recent messages to that channel.
-
After retrieving the first batch of messages, you can pass in a timestamp corresponding to the timestamp of most recent message you’ve received. That way, you’ll only retrieve newer messages than the ones you’ve already seen.
-
3. Putting it all together in swatcord.py
|
Note that to connect to the messaging server, you’ll need to run your
|
The message.py file should contain a class definition for a class named
Message, and messagecollection.py should contain a class definition for a
class named MessageCollection. You will need to import these classes into
your swatcord.py file by typing:
from message import Message from messagecollection import MessageCollection
Even though your files are called message.py and
messagecollection.py, you import from message and messagecollection.
Python adds the .py automatically when looking for the file.
|
Like prior labs, this lab will present the user with a menu-based interface to make selections. The menu should look something like:
Please select one of the following choices: 1. Get channel list 2. Join channel as sender 3. Join channel as receiver 0. Quit Enter selection:
You cannot assume that the user will type in an integer for this prompt. You also cannot assume that if they do type in an integer, it’ll be a valid menu selection. You need to validate that the user inputs a valid menu option.
Depending on what the user selects, you should do one of the following things:
-
Get channel list: Print a list of the server’s available chat channels. The providedchannel_list()function will do much of the work for you here. It will give you a comma-separated list of channels, which you’ll need to format for the user. For example:Please select one of the following choices: 1. Get channel list 2. Join channel as sender 3. Join channel as receiver 0. Quit Enter selection: 1 Available channels: * general * music * sports * testbot
-
Join channel as sender: Prompt the user for the name of the channel they’d like to join and the nickname they’d like to use. Then, repeatedly prompt the user to enter messages. For each message body they type, create aMessageobject and send it using thesend_messagefunction.Note that when you create the message, you only need to set the nickname of the user to the string that they selected. The server will automatically add the "real" (system-wide) username to the message when it delivers it.
If the user types
/leave, don’t send it as a message. Instead, take them out of sender mode and return to the main menu.When prompting for the channel name, you need to validate that the channel exists.
-
Join channel as receiver: Prompt the user for the name of the channel they’d like to retrieve messages from. Then, repeatedly use thereceive_messagesfunction to retrieve messages from the selected channel. For the first message retrieval (each time the user selects option 3), you’ll want to first request a timestamp of 0.0, which will retrieve up to the 20 most recent messages. For all subsequent retrievals, pass the timestamp of the most recent message you’ve previously retrieved.You do not need to save the timestamp after returning to the main menu. If the user joins the same channel again in the future, they should start from timestamp 0.0 again the next time.
Because a call to
receive_messagespauses your program until messages come in from the network, there’s not an easy way (e.g., typing a command) for the user to express that they’d like to return to the main menu. To solve this problem, you can wrap calls toreceive_messagesin atry/exceptstructure to catch theKeyboardInterruptexception. That exception gets triggered when the user pressesCTRL-C. Using this method will look something like:print("To return to the main menu, press CTRL-C") try: # Retrieve messages here (this may pause your program) except KeyboardInterrupt: print("Returning to main menu...")After interrupting a call to
receive_messagesusingCTRL-Cthis way, your connection object will no longer be valid, so you should call.close()on it and then create a new one prior to making any additional library calls (e.g., reassign yourconnectionvariable in main with a new call toconnect_to_server):connection = connect_to_server()
When prompting for the channel name, you need to validate that the channel exists.
-
Quit: Call.closeon your connection object and then exit the program.
3.1. Example Output
For this lab, the behavior is best demonstrated as a video:
Note that you must be logged in via your @swarthmore.edu Google account to
access this recording.
3.2. Testing Message Reception
The server will send an automated message to the "testbot" channel every 10 seconds. Joining that channel might help you to test receiving messages if nobody else is online to exchange messages with you.
4. Requirements
|
The code you submit for labs is expected to follow good style practices, and to meet one of the course standards, you’ll need to demonstrate good style on six or more of the lab assignments across the semester. To meet the good style expectations, you should:
In addition, you’ll need to demonstrate good top-down design practices on two or more lab assignments across the semester. To meet the top-down design expectations, you should:
|
Your program should meet the following requirements:
-
Your implementation of the
Messageclass should be in the filemessage.pyand its methods should behave as described in section 1.2. -
Your implementation of the
MessageCollectionclass should be in the filemessagecollection.pyand its methods should behave as described in section 1.3. -
Your
message.pyandmessagecollection.pyfiles should contain tests of the methods you’ve written in those files as described throughout section 1. -
Your
swatcord.pyshould contain amainfunction that presents the user with a menu of choices as described in section 3. -
Your
swatcord.pyshould validate that…-
When the user enters a menu option, they type a valid integer.
-
When the user enters a channel name to join (either as sender or receiver), they enter a valid channel name string.
-
-
Your implementation of
swatcord.pyshould allow the user to list the available chat channels. -
Your implementation of
swatcord.pyshould allow the user to join a channel for sending and subsequently send messages to the specified channel as described in section 3. -
Your implementation of
swatcord.pyshould allow the user to join a channel for receiving and subsequently display all messages sent to that channel until the user pressesCTRL-Cas described in section 3.