A TeamSpeak 3 viewer with Python & Javascript

The Problem

My gaming clan started using TeamSpeak 3 (TS3) for voice communications, so it wasn't long before we wanted to see who was on the TS3 server from the clan's server status page. Long ago, before I met Python, I had built the clan a server status page in PHP. This consisted of cobbling together various home-made and 3rd party PHP scripts for querying game servers (Call of Duty, Battlefield) and voice servers (TeamSpeak 2 and Mumble). But TeamSpeak 3 was a new one for us, and I didn't have anything to query that. My interests in PHP are long behind me, but we needed to add a TS3 viewer to the PHP page. The gaming clan's web hosting is pretty vanilla; in other words PHP is the first class citizen. If I really wanted to host a Python app I probably could have resorted to Fast CGI or something. But I had no experience in that and no desire to go that way.

I briefly thought about finding a 3rd party PHP library to query a TS3 server. The libraries are out there, but they are as you might expect: overly complicated and/or pretty amateurish (no public source code repository). I even considered writing my own PHP code to do the job, so I started looking for any documentation on the TS3 server query protocol. Luckily, there is a TS3 query protocol document, and it is fairly decent.

But, I just could not bring myself to write PHP again. On top of this, the gaming clan's shared hosting blocks non-standard ports. If I did have a PHP solution, the outgoing query to the TS3 server would have been blocked by the host's firewall. It is a hassle to contact their technical support and try to find a person who knows what a port is and get it unblocked (we've had to do this over and over as each game comes out). Thus it ultimately boiled down to me wanting to do this in Python. For me, life is too short to write PHP scripts.

I started thinking about writing a query application in Python using my dedicated server that I use to host a few Django powered websites. At first I thought I'd generate the server status HTML on my server and display it in an <iframe> on the gaming clan's server. But then it hit me that all I really needed to do is have my Django application output a representation of the TS3 server status in JSON, and then perhaps I could find a slick jQuery tree menu to display the status graphically. I really liked this idea, so here is a post about the twists and turns I took implementing it.

The Javascript

My searching turned up several jQuery tree menu plugins, but in the end I settled on dynatree. Dynatree had clear documentation I could understand, it seems to be actively maintained, and it can generate a menu from JSON. After one evening of reading the docs, I built a static test HTML page that could display a tree menu built from JSON. Here the Javascript code I put in the test page's <head> section:

var ts3_data = [
   {title: "Phantom Aces", isFolder: true, expand: true,
      children: [
         {title: "MW3", isFolder: true, expand: true,
            children: [
               {title: "Hogan", icon: "client.png"},
               {title: "Fritz!!", icon: "client.png"}
            ]
         },
         {title: "COD4", isFolder: true, expand: true,
            children: [
               {title: "Klink", icon: "client.png"}
            ]
         },
         {title: "Away", isFolder: true, children: [], expand: true}
     ]
   }
];

$(function(){
   $("#ts3-tree").dynatree({
      persist: false,
      children: ts3_data
   });
 });

Note that client.png is a small icon I found that I use in place of dynatree's default file icon to represent TS3 clients. If I omitted the icon attribute, the TS3 client would have appeared as a small file icon. Channels appear as folder icons, and this didn't seem to unreasonable to me. In other words I had no idea what a channel icon would look like. A folder was fine.

With dynatree, you don't need a lot of HTML markup, it does all the heavy lifting. You simply have to give it an empty <div> tag it can render into.

<body>
   <div id="ts3-tree"></div>
</body>
</html>

Here is a screenshot of the static test page in action.

/images/011-tree1.png

Nice! Thanks dynatree! Now all I need to do is figure out how to dynamically generate the JSON data and get it into the gaming clan's server status page.

The Python

Looking through the TS3 protocol documentation I was somewhat surprised to see that TS3 used the Telnet protocol for queries. So from my trusty shell I telnet'ed into the TS3 server and played with the available commands. I made notes on what commands I needed to issue to build my status display.

My experiments worked, and I could see a path forward, but there were still some kinks to be worked out with the TS3 protocol. The data it sent back was escaped in a strange way for one thing. I would have to post-process the data in Python before I could use it. I didn't want to reinvent the wheel, so I did a quick search for Python libraries for working with TS3. I found a few, but quickly settled on Andrew William's python-ts3 library. It was small, easy to understand, had tests, and a GitHub page. Perfect.

One of the great things about Python, of course, is the interactive shell. Armed with the TS3 protocol documentation, python-ts3, and the Python shell, I was able to interactively connect to the TS3 server and poke around again. This time I was sitting above telnet using python-ts3 and I confirmed it would do the job for me.

Another evening was spent coding up a Django view to query the TS3 server using python-ts3 and to output the channel status as JSON.

from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseServerError
from django.utils import simplejson
import ts3

CACHE_KEY = 'ts3-json'
CACHE_TIMEOUT = 2 * 60

def ts3_query(request):
    """
    Query the TeamSpeak3 server for status, and output a JSON
    representation.

    The JSON we return is targeted towards the jQuery plugin Dynatree
    http://code.google.com/p/dynatree/

    """
    # Do we have the result cached?
    result = cache.get(CACHE_KEY)
    if result:
        return HttpResponse(result, content_type='application/json')

    # Cache miss, go query the remote server

    try:
        svr = ts3.TS3Server(settings.TS3_IP, settings.TS3_PORT,
                settings.TS3_VID)
    except ts3.ConnectionError:
        return HttpResponseServerError()

    response = svr.send_command('serverinfo')
    if response.response['msg'] != 'ok':
        return HttpResponseServerError()
    svr_info = response.data[0]

    response = svr.send_command('channellist')
    if response.response['msg'] != 'ok':
        return HttpResponseServerError()
    channel_list = response.data

    response = svr.send_command('clientlist')
    if response.response['msg'] != 'ok':
        return HttpResponseServerError()
    client_list = response.data

    # Start building the channel / client tree.
    # We save tree nodes in a dictionary, keyed by their id so we can find
    # them later in order to support arbitrary channel hierarchies.
    channels = {}

    # Build the root, or channel 0
    channels[0] = {
        'title': svr_info['virtualserver_name'],
        'isFolder': True,
        'expand': True,
        'children': []
    }

    # Add the channels to our tree

    for channel in channel_list:
        node = {
            'title': channel['channel_name'],
            'isFolder': True,
            'expand': True,
            'children': []
        }
        parent = channels[int(channel['pid'])]
        parent['children'].append(node)
        channels[int(channel['cid'])] = node

    # Add the clients to the tree

    for client in client_list:
        if client['client_type'] == '0':
            node = {
                'title': client['client_nickname'],
                'icon': 'client.png'
            }
            channel = channels[int(client['cid'])]
            channel['children'].append(node)

    tree = [channels[0]]

    # convert to JSON
    json = simplejson.dumps(tree)

    cache.set(CACHE_KEY, json, CACHE_TIMEOUT)

    return HttpResponse(json, content_type='application/json')

I have to make three queries to the TS3 server to get all the information I need. The serverinfo command is issued to retrieve the TS3 virtual server's name. The channellist command retrieves the list of channels. The clientlist command gets the list of TS3 clients that are currently connected. For more information on these three commands see the TS3 query protocol document.

The only real tricky part of this code was figuring out how to represent an arbitrary, deeply-nested channel tree in Python. I ended up guessing that cid meant channel ID and pid meant parent ID in the TS3 query data. I squirrel away the channels in a channels dictionary, keyed by channel ID. The root channel has an ID of 0. While iterating over the channel list, I can retrieve the parent channel from the channels dictionary by ID and append the new channel to the parent's children list. Clients are handled the same way, but have different attributes. By inspecting the clientlist data in the Python shell, I noticed that my Telnet client also showed up in that list. However it had a client_type of 1, whereas the normal PC clients had a client_type of 0.

I decided to cache the results for 2 minutes to reduce hits on the TS3 server, as it has flood protection. This probably isn't needed given the size of our gaming clan, but Django makes it easy to do, so why not?

Putting it all together

At this point I knew how to use my Django application to query the TS3 server and build status in JSON format. I also knew what the Javascript and HTML on the gaming clan's server status page (written in PHP) had to look like to render that JSON status.

The problem was the server status page was on one server, and my Django application was on another. At first I thought it would be no problem for the Javascript to do a GET on my Django server and retrieve the JSON. However I had some vague memory of the browser security model, and after some googling I was reminded of the same origin policy. Rats. That wasn't going to work.

I briefly researched JSONP, which is the technique that Facebook & Google use to embed those little "like" and "+1" buttons on your web pages. But in the end it was just as easy to have the PHP script make the GET request to my Django application using a file_get_contents() call. The PHP can then embed the JSON directly into the server status page:

$ts3_source = 'http://example.com/ts3/';
$ts3_json = file_get_contents($ts3_source);

require_once 'header.php';

And in header.php, some HTML sprinkled with some PHP:

<script type="text/javascript">
   var ts3_data = <?php echo $ts3_json; ?>;

   $(function(){
      $("#ts3-tree").dynatree({
         persist: false,
         children: ts3_data
      });
    });
</script>

That did the trick. In the end I had to touch a little PHP, but it was tolerable. That was a very round-about solution to building a TS3 viewer in Python and Javascript. While I doubt you will have the same strange requirements that I had (multiple servers), I hope you can see how to combine a few technologies to make a TS3 viewer in Python.

Read and Post Comments

Who's Online with Redis & Python, a slight return

In a previous post, I blogged about building a "Who's Online" feature using Redis and Python with redis-py. I've been integrating Celery into my website, and I stumbled across this old code. Since I made that post, I discovered yet another cool feature in Redis: sorted sets. So here is an even better way of implementing this feature using Redis sorted sets.

A sorted set in Redis is like a regular set, but each member has a numeric score. When you add a member to a sorted set, you also specify the score for that member. You can then retrieve set members if their score falls into a certain range. You can also easily remove members outside a given score range.

For a "Who's Online" feature, we need a sorted set to represent the set of all users online. Whenever we see a user, we insert that user into the set along with the current time as their score. This is accomplished with the Redis zadd command. If the user is already in the set, zadd simply updates their score with the current time.

To obtain the curret list of who's online, we use the zrangebyscore command to retrieve the list of users who's score (time) lies between, say, 15 minutes ago, until now.

Periodically, we need to remove stale members from the set. This can be accomplished by using the zremrangebyscore command. This command will remove all members that have a score between minimum and maximum values. In this case, we can use the beginning of time for the minimum, and 15 minutes ago for the maximum.

That's really it in a nutshell. This is much simpler than my previous solution which used two sets.

So let's look at some code. The first problem we need to solve is how to convert a Python datetime object into a score. This can be accomplished by converting the datetime into a POSIX timestamp integer, which is the number of seconds from the UNIX epoch of January 1, 1970.

import datetime
import time

def to_timestamp(dt):
    """
    Turn the supplied datetime object into a UNIX timestamp integer.

    """
    return int(time.mktime(dt.timetuple()))

With that handy function, here are some examples of the operations described above.

import redis

# Redis set keys:
USER_SET_KEY = "whos_online:users"

# the period over which we collect who's online stats:
MAX_AGE = datetime.timedelta(minutes=15)

# obtain a connection to redis:
conn = redis.StrictRedis()

# add/update a user to the who's online set:

username = "sally"
ts = to_timestamp(datetime.datetime.now())
conn.zadd(USER_SET_KEY, ts, username)

# retrieve the list of users who have been active in the last MAX_AGE minutes

now = datetime.datetime.now()
min = to_timestamp(now - MAX_AGE)
max = to_timestamp(now)

whos_online = conn.zrangebyscore(USER_SET_KEY, min, max)

# e.g. whos_online = ['sally', 'harry', 'joe']

# periodically remove stale members

cutoff = to_timestamp(datetime.datetime.now() - MAX_AGE)
conn.zremrangebyscore(USER_SET_KEY, 0, cutoff)
Read and Post Comments

Upgrading Trac on Windows Gotchas

At work, we are outfitted with Windows servers. Despite this obstacle, I managed to install Trac and Subversion a few years ago. During a break in the action, we decided to update Subversion (SVN) and Trac. Since we are on Windows, this means we have to rely on the kindness of strangers for Subversion binaries. I ran into a couple of gotchas I'd like to document here to help anyone else who runs into these.

I managed to get Subversion and Trac up and running without any real problems. However when Trac needed to access SVN to display changesets or a timeline, for example, I got an error:

tracerror: unsupported version control system "svn" no module named _fs

After some googling, I finally found that this issue is documented on the Trac wiki, but it was kind of hard to find. To fix this problem, you have to rename the Python SVN binding's DLLs to *.pyd. Specifically, change the libsvn/*.dll files to libsvn/*.pyd, but don't change the name of libsvn_swig_py-1.dll. I'd really like to hear an explanation of why one needs to do this. Why doesn't the Python-Windows build process do this for you?

The second problem I ran into dealt with mod_wsgi on Windows. Originally, a few years ago, I setup Trac to run under mod_python. mod_python has long been out of favor, so I decided to cut over to mod_wsgi. On my Linux boxes, I always run mod_wsgi in daemon mode. When I tried to configure this on Windows, Apache complained about an unknown directive WSGIDaemonProcess. It turns out that this mode is not supported on Windows. You'll have to use the embedded mode on Windows.

Read and Post Comments

Implementing OAuth using Google's Python Client Library

My Django powered website allows users to submit events for a site calendar that is built upon Google Calendar. After an admin approves events, I use Google's Python Client Library to add, delete, or update events on the Google calendar associated with my personal Google account. I wrote this application a few years ago, and it used the ClientLogin method for authentication. I recently decided to upgrade this to the OAuth authentication method. The ClientLogin method isn't very secure and it doesn't play well with Google's two-step verification. After hearing about a friend who had his GMail account compromised and all his email deleted I decided it was long past due to get two-step verification on my account. But first I needed to upgrade my web application to OAuth.

In this post I'll boil down the code I used to implement the elaborate OAuth dance. It really isn't that much code, but the Google documentation is somewhat confusing and scattered across a bewildering number of documents. I found at least one error in the documentation that I will point out. Although I am using Django, I will omit details specific to Django where I can.

In addition to switching from ClientLogin to OAuth, I also upgraded to version 2.0 of the Google Data API. This had more implications for my calendar-specific code, and perhaps I can go over that in a future post.

Getting started and registering with Google

To understand the basics of OAuth, I suggest you read OAuth 1.0 for Web Applications. I decided to go for maximum security and use RSA-SHA1 signing on all my requests to Google. This requires that I verify my domain and then register my application with Google, which includes uploading a security certificate. Google provides documentation that describes how you can create a self-signing private key and certificate using OpenSSL.

Fetching a Request Token and authorizing access

To perform the first part of the OAuth dance, you must ask Google for a request token. When you make this request, you state the "scope" of your future work by listing the Google resources you are going to access. In our case, this is the calendar resources. You also provide a "consumer key" that Google assigned to you when you registered your application. This allows Google to retrieve the security certificate you previously uploaded when you registered. This is very important because this request is going to be signed with your private key. Fortunately the Python library takes care of all the signing details, you simply must provide your private key in PEM format. And finally, you provide a "callback URL" that Google will send your browser to after you (or your users) have manually authorized this request.

Once you have received the request token from Google, you have to squirrel it away somewhere, then redirect your (or your user's) browser to a Google authorization page. Once the user has authorized your application, Google sends the browser to the callback URL to continue the process. Here I show the distilled code I used that asks for a request token, then sends the user to the authorization page.

import gdata.gauth
from gdata.calendar_resource.client import CalendarResourceClient

USER_AGENT = 'mydomain-myapp-v1' # my made up user agent string

client = CalendarResourceClient(None, source=USER_AGENT)

# obtain my private key that I saved previously on the filesystem:
with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f:
    rsa_key = f.read()

# Ask for a request token:
# scopes - a list of scope strings that the request token is for. See
# http://code.google.com/apis/gdata/faq.html#AuthScopes
# callback_url - URL to send the user after authorizing our app

scopes = ['https://www.google.com/calendar/feeds/']
callback_url = 'http://example.com/some/url/to/callback'

request_token = client.GetOAuthToken(
        scopes,
        callback_url,
        settings.GOOGLE_OAUTH_CONSUMER_KEY, # from the registration process
        rsa_private_key=rsa_key)

# Before redirecting, save the request token somewhere; here I place it in
# the session (this line is Django specific):
request.session[REQ_TOKEN_SESSION_KEY] = request_token

# Generate the authorization URL.
# Despite the documentation, don't do this:
#    auth_url = request_token.generate_authorization_url(domain=None)
# Do this instead if you are not using a Google Apps domain:
auth_url = request_token.generate_authorization_url()

# Now redirect the user somehow to the auth_url; here is how you might do
# it in Django:
return HttpResponseRedirect(auth_url)

A couple of notes on the above:

  • You don't have to use CalendarResourceClient, it just made the most sense for me since I am doing calendar stuff later on. Any class that inherits from gdata.client.GDClient will work. You might be able to use that class directly. Google uses gdata.docs.client.DocsClient in their examples.
  • I chose to store my private key in a file rather than the database. If you do so, it's probably a good idea to make the file readable only to the user your webserver runs your application as.
  • After getting the request token you must save it somehow. You can save it in the session, the database, or perhaps a file. Since this is only temporary, I chose to save it in the session. The code I have here is Django specific.
  • When generating the authorization URL, don't pass in domain=None if you aren't using a Google Apps domain like the documentation states. This appears to be an error in the documentation. Just omit it and let it use the default value of "default" (see the source code).
  • After using the request token to generate the authorization URL, redirect the browser to it.

Extracting and upgrading to an Access Token

The user will then be taken to a Google authorization page. The page will show the user what parts of their Google account your application is trying to access using the information you provided in the scopes parameter. If the user accepts, Google will then redirect the browser to your callback URL where we can complete the process.

The code running at our callback URL must retrieve the request token that we saved earlier, and combine that with certain GET parameters Google attached to our callback URL. This is all done for us by the Python library. We then send this new token back to Google to upgrade it to an actual access token. If this succeeds, we can then save this new access token in our database for use in subsequent Google API operations. The access token is a Python object, so you can serialize it use the pickle module, or use routines provided by Google (shown below).

# Code running at our callback URL:
# Retrieve the request token we saved earlier in our session
saved_token = request.session[REQ_TOKEN_SESSION_KEY]

# Authorize it by combining it with GET parameters received from Google
request_token = gdata.gauth.AuthorizeRequestToken(saved_token,
                    request.build_absolute_uri())

# Upgrade it to an access token
client = CalendarResourceClient(None, source=USER_AGENT)
access_token = client.GetAccessToken(request_token)

# Now save access_token somewhere, e.g. a database. So first serialize it:
access_token_str =  gdata.gauth.TokenToBlob(access_token)

# Save to database (details omitted)

Some notes on the above code:

  • Once called back, our code must retrieve the request token we saved in our session. The code shown is specific to Django.
  • We then combine this saved request token with certain GET parameters that Google added to our callback URL. The AuthorizeRequestToken function takes care of those details for us. The second argument to that function requires the full URL including GET parameters as a string. Here I populate that argument by using a Django-specific method of retrieving that information.
  • Finally, you upgrade your token to an access token by making one last call to Google. You should now save a serialized version of this access token in your database for future use.

Using your shiny new Access Token

Once you have saved your access token, you won't have to do this crazy dance again until the token either expires, or the user revokes your application's access to the Google account. To use it in a calendar operation, for example, you simply retrieve it from your database, deserialize it, and then use it to create a CalendarClient.

from gdata.calendar.client import CalendarClient

# retrieve access token from the database:
access_token_str = ...
access_token = gdata.gauth.TokenFromBlob(access_token_str)

client = CalendarClient(source=USER_AGENT, auth_token=access_token)

# now use client to make calendar operations...

Conclusion

The main reason I wrote this blog post is I wanted to show a concrete example of using RSA-SHA1 and version 2.0 of the Google API together. All of the information I have presented is in the Google documentation, but it is spread across several documents and jumbled up with example code for version 1.0 and HMAC-SHA1. Do not be afraid to look at the source code for the Python client library. Despite Google's strange habit of ignoring PEP-8 and using LongJavaLikeMethodNames, the code is logical and easy to read. Their library is built up in layers, and you may have to dip down a few levels to find out what is going on, but it is fairly straightforward to read if you combine it with their online documentation.

I hope someone finds this useful. Your feedback is welcome.

Read and Post Comments

Contributing to open source - a success story and advice for newbies

Recently, my team at work found a bug in Subversion, I submitted a patch, and it was accepted! This was very exciting for me so I thought I would share this story in the hopes of inspiring others to contribute to open source projects. It may not be as hard as you might think!

The Bug

We use Subversion at work for revision control. My colleague and I were trying to merge a branch back to trunk when we ran into some strange behavior. We make use of Subversion properties, which allow you to attach arbitrary metadata to files and directories. Our project has to deliver our source code and documentation to the customer in a required directory format (can you guess who our customer is?). However not all files need to be sent to the customer. To solve this problem we use a simple "yes/no" delivery property on each file to control whether it is delivered or not. Before making a delivery, a script is run that prunes out the files that have the delivery flag set to "no".

When our merge was completed, many files were marked with having merge conflicts on the delivery property. Looking through the logs it was discovered that after we had made our branch, someone had changed the delivery property on some files to "yes" on the trunk. Someone else had also changed the delivery property independently to "yes" on the branch. When we attempted to merge the branch back to trunk, we were getting merge conflicts, even though we were trying to change the delivery property value to "yes" on both the trunk and branch. Why was this a conflict? This didn't seem quite right.

I signed up for the Subversion user's mailing list and made a short post summarizing our issue. I later learned that it is proper etiquette to attach a bash script that can demonstrate the problem. Despite this, a Subversion developer took interest in my post and created a test script in an attempt to reproduce our issue. At first it looked like he could not reproduce the problem. However another helpful user pointed out a bug in his script. Once this was fixed, the developer declared our problem a genuine bug and created a ticket for it in the issue tracker.

The Patch

Impressed by all of this, I thanked him for his efforts and tentatively asked if I could help. The developer told me which file and function he thought the problem might be in. I downloaded the Subversion source and began looking at the code. I was fairly impressed with the code quality, so I decided I would try to create a patch for the bug over the weekend. We really wanted that bug fixed, and I was genuinely curious to see if I would be able to figure something out. It would be an interesting challenge and a test of my novice open source skills.

When the weekend came I began a more thorough examination of the Subversion website. The Subversion team has done a great job in providing documentation on their development process. This includes a contributing guide and patch submittal process. I also discovered they had recently added a makefile that downloaded the Subversion source code and the source for all of Subversion's dependencies. The makefile then builds everything with debug turned on. Wow! It took me a few tries to get this working, but the problems were because I did not have all the development tools installed on my Ubuntu box. Once this was sorted, everything went smoothly, and in a matter of minutes I had a Subversion executable I could run under the gdb debugger. Nice!

I studied the code for about an hour, peeking and poking at a few things in the debugger. I used the script the developer wrote to recreate the problem. I wasn't quite sure what I was doing, as I was brand new to this code base. But the code was clearly written and commented well. My goal was to get a patch that was in the 80-100% complete range. I wanted to do enough work that a core developer would be able to see what I was doing and either commit it outright or easily fill in the parts that I missed. After a while I thought I had a solution and generated a patch. I sent it to the Subversion developer's mailing list as per the contributing guide.

The Wait

Next I began probably the worst part for a contributor. I had to wait and see if I got any feedback. On some open source projects a patch may languish for months. It all depends on the number of developers and how busy they are. My chances didn't look good as the developers were in the initial stages of getting a beta version of 1.7 out the door. It was also not clear to me who "owned" the issue tracker. On some projects, the issue tracker is wide open to the community. Was I supposed to update the ticket? I wasn't quite sure, and the contributing guide was silent on this issue. I eventually concluded I was not; it looked like only committers were using the tracker. Patches were being discussed on the mailing list instead of in the tracker. This is a bit different than some projects I am familiar with.

I didn't have to wait long. After a few days, the original developer who confirmed my bug took interest again. He looked at my patch, and thought I had missed something. He suggested a change and asked for my opinion. I looked at the code again; it seemed like a good change and I told him I agreed. I also warned him I was brand new to the code, and to take my opinion with a grain a salt. After running my change against the tests, he then committed my patch! One small victory for open source!

Final Thoughts

So what went right here? I have to hand it to the Subversion team. They have been in business a long time, and they have excellent documentation for people who want to contribute. The makefile they created that sets up a complete development environment most definitely tipped the scale for me and enabled me to create my patch. Without that I'm not sure I would have had the time or patience to get all that unfamiliar source code built. The Subversion team has really worked hard at lowering the barrier for new contributors.

My advice to people who want to contribute to open source but aren't quite sure how to go about doing it:

  • Spend some time reading the documentation. This includes any FAQ's and contributor guides (if any).
  • Monitor the user and developer mailing lists to get a feel for how the community operates. Each project has different customs and traditions.
  • You may also wish to hang out on the project's IRC channel for the same reason.
  • When writing on the mailing lists, be extremely concise and polite. You don't want to waste anyone's time, and you don't want to be seen as someone who thinks they are entitled to a fix. Just remember you are the new guy. You can't just barge in and make demands.
  • Ask how you can help. Nothing makes a developer happier when someone asks how they can help. Remember, most of the people in the community are volunteers.
  • Open source can sometimes be "noisy". There will be people who won't quite understand your issue and may hurriedly suggest an incorrect solution or give incomplete advice. Study their responses and be polite. You may also wish to resist the temptation to reply right away. This is especially hard when you are new and you don't know who the "real" developers are. However you should assume everyone is trying to help.
  • Finally, be patient. Again, most folks are volunteers. They have real jobs, families and lives. The project may also be preoccupied with other tasks, like getting a beta out the door. Now may not be a good time for a brand new feature, or your bug may not be considered a show stopper to the majority of the community.

A big thank-you to Stefan Sperling from the Subversion team who shepherded my bug report and patch through their process.

I hope this story encourages you to contribute to open source software!

Read and Post Comments

« Previous Page -- Next Page »