# Pyggle: An OpenGiggle client and server in Python. vim:ts=4 # Copyright 2001 Adam Sampson # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import socket, string, time, sys, whrandom from SocketServer import * def trimcrlf(s): """Remove a crlf or just a lf from the end of a string.""" if s[-2:] == "\r\n": return s[:-2] if s[-1:] == "\n": return s[:-1] return s class GiggleClient: """An OpenGiggle client. Example of use: g = Giggle.GiggleClient("giggle.opengiggle.org") print g.giggle() """ def __init__(self, server, port = 4700, clientid = "Pyggle 0.1"): """Construct a GiggleClient. Invoke as GiggleClient(server[, port[, clientid]])""" self.server = server self.port = port self.clientid = clientid def query(self): """Return a tuple (serverstring, gigglestring).""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.server, self.port)) s.send("GIGGLE/1.0 " + self.clientid + "\r\n") s.send("\r\n") r = "" while 1: d = s.recv(1024) if not d: break r = r + d rl = string.split(r, "\r\n") if len(rl) < 2: return ("", "") else: return (rl[0], rl[1]) def giggle(self): """Return a giggle.""" (s, g) = self.query() return g class GiggleRequestHandler(StreamRequestHandler): def readline(self): """Read a line from the client. Return None if an error occurs.""" try: return trimcrlf(self.rfile.readline()) except IOError: self.server.log("Incomplete request from " + self.client_address[0]) return None def handle(self): """Handle a client request.""" l = self.readline() if not l: return ls = string.split(l, " ", 1) if len(ls) < 2: client = "Unknown" else: client = ls[1] while 1: l = self.readline() if l is None: return if l == "": break self.server.log('"' + client + '" 1.0 ' + self.client_address[0]) self.wfile.write(self.server.response()) class GiggleServer(ForkingMixIn, TCPServer): """An OpenGiggle server.""" def log(self, message): """Write a timestamped message to the log file.""" t = time.strftime("%d/%m/%Y %H:%M:%S", time.gmtime(time.time())) self.logfile.write(t + " " + message + "\n") self.logfile.flush() def response(self): """Return a giggle response.""" # Reseed the generator, else each forked instance ends up with # the same state and you get the same giggle all the time. whrandom.seed() return "Pyggle 0.1\r\n" + whrandom.choice(self.giggles) + "\r\n" def __init__(self, gigglefile = "giggles.txt", logfile = "giggle.log", host = "localhost", port = 4700): """Construct a GiggleServer. Invoke as GiggleServer([gigglefile[, logfile[, host[, port]]]]) Call the main() method to start serving giggle requests. Specify logfile as None to log to stderr.""" try: self.giggles = map(trimcrlf, open(gigglefile).readlines()) except IOError: raise IOError, "unable to read giggles" if logfile: try: self.logfile = open(logfile, "a") except IOError: raise IOError, "unable to open log file" else: self.logfile = sys.stderr TCPServer.__init__(self, (host, port), GiggleRequestHandler) def main(self): """Start the server.""" self.log("Starting up") self.serve_forever() if __name__ == "__main__": # If this file is invoked rather than imported, start a giggle server # on port 4701. g = GiggleServer("giggles.txt", "giggled.log", "localhost", 4701) g.main()