HackTheNet Credit Juggler

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
HTN Credit Juggler
==================

A tool for the HackTheNet_ browser game to juggle with credits,
causing magic things to happen.

Original Perl script created by Gizmor.  Improved, refurbished
and eventually ported to Python by Y0Gi.

Tested with HTN version 2.5 Beta.

:Copyright: 2005 Gizmor, Y0Gi
:Date: 12-Dec-2005
:License: GNU General Public License
:Version: 0.4

.. _HackTheNet: http://www.hackthenet.org/
"""

from optparse import OptionParser
import re
import sys
import threading
import urllib
import urllib2


# Configuration
USER_AGENT = ('Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.7.12)'
              ' Gecko/20051010 Firefox/1.0.7')
URL_BASE = 'http://htn25.unkreativ.org/_htn.php'
TIMEOUT = 4.0


def repay_credit(url_repay, cid):
    urllib2.urlopen(url_repay + cid).close()
    print "Credit '%s' repayed." % cid

def main():
    print 'HTN Credit Juggler 0.4 by Gizmor and Y0Gi\n'

    # Create option parser and define options.
    parser = OptionParser(
        usage='%prog [options] <session ID>',
        version='HTN Credit Juggler 0.4',
        description='HTN Credit Juggler juggles with credits,'
                    'causing magic things to happen.')
    parser.add_option('-c', '--credit-count',
        type='int', dest='credit_count', default=3,
        help='number of credits (1-3) to raise (default: 3)',
        metavar='NUM')
    parser.add_option('-p', '--passes',
        type='int', dest='pass_count', default=50,
        help='number of passes (default: 50)', metavar='NUM')

    # Process options and arguments.
    options, args = parser.parse_args()
    if len(args) != 1:
        parser.print_help()
        parser.exit()
    session_id = args[0]

    # Set URLs.
    url_bank = '%s/bank/start?sid=%s' % (URL_BASE, session_id)
    url_raise = '%s/bank/raisecredit?sid=%s&secret=' % (URL_BASE, session_id)
    url_repay = '%s/bank/repaycredit?sid=%s&id=' % (URL_BASE, session_id)

    # Precompile regular expression patterns.
    re_raise_secret = re.compile('raisecredit\?.*?secret=([0-9a-f]+?)"')
    re_max_credit_amount = re.compile('\(max. ([\d\.]+) Credits pro Kredit\)')
    re_credit_id = re.compile('repaycredit\?.*?id=([0-9a-f]+?)"')

    # Customize the URL opener.
    opener = urllib2.build_opener()
    opener.addheaders = [('User-Agent', USER_AGENT), ('Referer', URL_BASE)]
    urllib2.install_opener(opener)

    # Main loop
    for pass_num in range(1, options.pass_count + 1):
        try:
            print '--- Pass #%d ---' % pass_num

            # Raise credits.
            for i in range(options.credit_count):
                # Retrieve HTML form.
                try:
                    data = urllib2.urlopen(url_bank).read()
                except urllib2.URLError, e:
                    print 'Could not retrieve HTML form:', e
                    continue

                # Extract challenge code and maximum
                # credit amount from document.
                try:
                    challenge = re_raise_secret.search(data).group(1)
                except AttributeError:
                    print 'Could not extract challenge code.'
                    continue
                try:
                    credit_amount = re_max_credit_amount.search(data) \
                                    .group(1).replace('.', '')
                except AttributeError:
                    print 'Could not extract maximum credit amount.'
                    continue

                # Actually raise a credit.
                try:
                    req = urllib2.Request(
                        url_raise + challenge,
                        urllib.urlencode({'credits': credit_amount}))
                    urllib2.urlopen(req).close()
                    print 'Credit in the amount of %sc raised.' \
                        % credit_amount
                except urllib2.URLError, e:
                    print 'Could not raise credit:', e

            # Fetch IDs of raised credits.
            try:
                data = urllib2.urlopen(url_bank).read()
            except urllib2.URLError, e:
                sys.exit('Could not fetch credits list: ' + str(e))
            credit_ids = re_credit_id.findall(data)
            print 'Currently raised credits: ', ', '.join(credit_ids)

            # Repay credits.
            threads = []
            for cid in credit_ids:
                threads.append(threading.Thread(
                    target=repay_credit, args=(url_repay, cid)))
                threads[-1].start()
            for t in threads:
                t.join(TIMEOUT)
        except KeyboardInterrupt:
            sys.exit('Aborting ...'
                ' you might need to kill the process manually.')

if __name__ == '__main__':
    main()