Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
474 lines (392 sloc) 16.1 KB
# Copyright (c) 2002 Sean R. Lynch <seanl@chaosring.org>
#
# This file is part of PythonVerse.
#
# PythonVerse 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.
#
# PythonVerse 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 PythonVerse; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys, os, asyncore, asynchat, socket, string, struct, stat, codecs
import transutil
# Global constants are all caps; global variables start with _
ENCODING = 'ISO-8859-1'
BALLOONXOFFSET = 15
HOME = os.path.expanduser('~/.OpenVerse')
ANIMDIR = os.path.join(HOME, 'anims')
DLDIR = os.path.join(HOME, 'download')
ICONDIR = os.path.join(HOME, 'icons')
IMAGEDIR = os.path.join(HOME, 'images')
OBJDIR = os.path.join(HOME, 'objects')
RIMAGEDIR = os.path.join(HOME, 'rimages')
ROOMDIR = os.path.join(HOME, 'rooms')
text_decode = codecs.lookup(ENCODING)[1]
def decode(s):
return text_decode(s)[0]
def checkcache(filename, size):
try: s = os.stat(filename)[stat.ST_SIZE]
except OSError: return None
else:
if s == size or size < 0: return filename
class DCC(asyncore.dispatcher):
def __init__(self, host, port, filename, size, progress_callback,
close_callback, sock=()):
asyncore.dispatcher.__init__(self, sock)
self.host = host
self.port = port
self.filename = filename
self.size = size
self.length = 0
self.outbuf = ''
self.buffer = ''
self.progress_callback = progress_callback
self.close_callback = close_callback
def __repr__(self):
return '<DCC %s %s:%d %d/%d>' % (self.filename, self.host, self.port,
self.length, self.size)
def handle_connect(self):
pass
def handle_write(self):
sent = self.send(self.outbuf)
self.outbuf = self.outbuf[sent:]
def writable(self):
return len(self.outbuf) > 0
def handle_close(self):
print self, 'closing'
self.close()
if self.close_callback is not None:
apply(self.close_callback, (self.tempfilename, self.filename,
self.size))
class DCCGet(DCC):
def __init__(self, host, port, filename, size,
progress_callback, close_callback):
DCC.__init__(self, host, port, filename, size, progress_callback,
close_callback)
self.tempfilename = os.path.join(RIMAGEDIR, filename)
self.file = open(self.tempfilename, 'wb')
self.size = size
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def handle_read(self):
data = self.recv(4096)
if data:
self.file.write(data)
self.length = self.length + len(data)
self.outbuf = self.outbuf + struct.pack('>I', self.length)
if self.progress_callback is not None:
apply(self.progress_callback, (self.length, self.tempfilename,
self.filename, self.size))
if self.length == self.size:
self.file.close()
class DCCSendPassive(DCC):
def __init__(self, host, port, filename, size, progress_callback,
close_callback):
DCC.__init__(self, host, port, filename, size, progress_callback,
close_callback)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
self.file = open(filename, 'rb')
self.outbuf = self.file.read(4096)
self.sent = 0
def handle_read(self):
data = self.recv(4)
print self, 'received %d bytes' % len(data)
if data:
data = self.buffer + data
# Drop all but one length received
drop = len(data) / 4 * 4 - 4
data = data[drop:]
if len(data) >= 4:
# Get the number of bytes received by the client
(self.length,) = struct.unpack('>I', data[:4])
self.buffer = data[4:]
print self
if self.progress_callback is not None:
apply(self.progress_callback, (self.length, self.filename,
self.size))
if self.length == self.size:
self.file.close()
self.handle_close()
def handle_write(self):
#if self.length < self.sent: return
sent = self.send(self.outbuf)
self.outbuf = self.outbuf[sent:]
self.sent = self.sent + sent
if len(self.outbuf) < 4096:
self.outbuf = self.outbuf + self.file.read(4096)
class ServerConnection(transutil.Connection):
def __init__(self, host, port, client, nick, avatar):
transutil.Connection.__init__(self)
self.host = host
self.port = port
self.pending_images = {}
self.client = client
self.images = {}
self.nick = nick
if type(avatar) == type(''): avdata = self.parse_anim(avatar)
else: avdata = avatar
self.avatar_filename = avdata[0]
self.nx = avdata[1]
self.ny = avdata[2]
self.bx = avdata[3]
self.by = avdata[4]
# Connect last
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
# Handlers for asynchat
def handle_connect(self):
size = os.stat(self.avatar_filename)[stat.ST_SIZE]
self.write("AUTH %s %d %d %s %d %d %d %d %d\r\n" %
(self.nick.encode(ENCODING, 'replace'), 320, 200, os.path.basename(self.avatar_filename), self.nx, self.ny,
size, self.bx, self.by))
def handle_close(self):
transutil.Connection.handle_close(self)
self.client.close()
# Utility functions
def set_client(self, client):
"""Change the client object that this object calls to"""
self.client = client
def debug(self, info):
self.client.debug(info)
def get_image(self, filename, size, command, pcallback, callback, args=()):
# Prevent possible embedded '/' attacks
filename = os.path.basename(filename)
if filename == 'default.gif': size = -1
try: image = self.images[filename, size]
except KeyError:
# Check in locally cached images
file = checkcache(os.path.join(RIMAGEDIR, filename), size)
if file is None:
# Check my own avatars as well
file = checkcache(os.path.join(IMAGEDIR, filename), size)
if file is not None:
image = self.client.newimage(file)
self.images[filename, size] = image
return image
blob = (pcallback, callback, args)
# Take the callback out if it's already in there
for pending in self.pending_images.values():
if blob in pending: pending.remove(blob)
try: self.pending_images[filename, size].append(blob)
except KeyError: self.pending_images[filename, size] = [blob]
else: pending.append(blob)
self.write('%s %s\r\n' % (command, filename))
else: return image
def progress_callback(self, length, tempfilename, filename, size):
for pcallback, callback, args in self.pending_images[(filename, size)]:
if pcallback: apply(pcallback, args + (length,tempfilename,size))
def image_callback(self, tempfilename, filename, size):
image = self.client.newimage(tempfilename)
self.images[(filename, size)] = image
for pcallback, callback, args in self.pending_images[(filename, size)]:
apply(callback, args + (image,))
# Client functions
def getnick(self):
return self.nick
def gethostport(self):
return (self.host, self.port)
def new_connect(self, host, port):
self.close()
self.host = host
self.port = port
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def move(self, pos):
x, y = pos
self.write('MOVE %s %d %d 20\r\n' % (self.nick.encode(ENCODING, 'replace'), x, y))
def push(self):
self.write('PUSH 100\r\n')
def effect(self, action):
self.write('EFFECT %s\r\n' % action.lower())
def privmsg(self, nicks, text):
if type(nicks) == type(''): nicks = [nicks]
for n in nicks:
self.write('PRIVMSG %s %s\r\n' % (n.encode(ENCODING, 'replace'), text))
return self.nick
def url(self, nicks, url):
if type(nicks) == type(''): nicks = [nicks]
for n in nicks:
self.write('URL %s %s\r\n' % (n.encode(ENCODING, 'replace'), url))
def chat(self, text):
self.write('CHAT %s\r\n' % text.encode(ENCODING, 'replace'))
def set_nick(self, nick):
self.nick = nick
self.write('NICK %s\r\n' % nick)
def ignore(self, what, nick):
self.write('IGNORE %s %s\r\n' % (what.upper(), nick))
def unignore(self, what, nick):
self.write('UNIGNORE %s %s\r\n' % (what.upper(), nick))
def quote(self, text):
"""Send raw commands to the server"""
self.write('%s\r\n' % text)
def set_avatar(self, avatar):
if type(avatar) == type(''): av = self.parse_anim(avatar)
else: av = avatar
try: size = os.stat(os.path.join(IMAGEDIR, av[0]))[stat.ST_SIZE]
except OSError, info: self.client.debug(info)
else:
self.avatar_filename = av[0]
self.nx = av[1]
self.ny = av[2]
self.bx = av[3]
self.by = av[4]
self.write('AVATAR %s %d %d %d %d %d\r\n' %
(os.path.basename(av[0]), av[1], av[2], size, av[3], av[4]))
def parse_anim(self, avatar):
"""Parse the avatar definition file for information"""
try: avfile = open(os.path.join(ANIMDIR, avatar), 'r')
except:
try: avfile = open(os.path.join(ANIMDIR, avatar + '.av'), 'r')
except:
print "Avatar", avatar, "not found."
if self.avatar_filename is not None:
return [self.avatar_filename,
self.nx, self.ny, self.bx, self.by]
return ["default.gif", 0, 36, 24, 6]
animdata = avfile.readlines()
avfile.close()
for animitem in animdata:
# parse the whole file for future addition of animation
splitanimitem = animitem.split()
if splitanimitem[1] == "MV(anim.x_off)":
nx = int(splitanimitem[2])
if splitanimitem[1] == "MV(anim.y_off)":
ny = int(splitanimitem[2])
if splitanimitem[1] == "MV(anim.baloon_x)":
bx = int(splitanimitem[2])
if splitanimitem[1] == "MV(anim.baloon_y)":
by = int(splitanimitem[2])
if splitanimitem[1] == "MV(anim.0)":
avimage = splitanimitem[2].strip()
return os.path.join(IMAGEDIR, avimage), nx, ny, bx, by
def whois(self, nick):
self.write('WHOIS %s\r\n' % nick)
def whichmouseover(self, name):
self.client.setmouseover(name)
# Command handlers
def cmd_ABOVE(self, line):
"""Raise the named object to the top of the stacking order"""
self.client.raise_object(line.split()[1])
def cmd_CHAT(self, line):
cmd, nick, text = line.split(' ', 2)
self.client.chat(decode(nick), decode(text))
def cmd_SCHAT(self, line):
cmd, emote, nick, text = line.split(' ', 3)
self.client.chat(nick, '*%s* %s' % (emote, decode(text)))
def cmd_MOVE(self, line):
cmd, nick, x, y, speed = line.split()
self.client.move_avatar(nick, int(x), int(y), int(speed))
def cmd_EFFECT(self, line):
cmd, nick, action = line.split()
self.client.effect(decode(nick), action)
def cmd_PRIVMSG(self, line):
cmd, nick, text = line.split(' ', 2)
self.client.privmsg(nick, text)
def cmd_ROOM(self, line):
"""Set a new background image"""
cmd, filename, filesize = line.split()
filesize = int(filesize)
image = self.get_image(filename, filesize, 'DCCSENDROOM',
self.client.background_progress,
self.client.background_image)
if image is not None: self.client.background_image(image)
def cmd_AVATAR(self, line):
cmd, nick, filename, nx, ny, size, bx, by = line.split()
nick = decode(nick)
nx = int(nx)
ny = int(ny)
size = int(size)
bx = int(bx)
by = int(by)
image = self.get_image(filename, size, 'DCCSENDAV',
self.client.avatar_progress,
self.client.avatar_image, (nick,))
if image is None: image = self.client.newimage()
# Need to shift 15 pixels to the left because OV uses the edge of
# the balloon rather than the arrow as the offset point. I do this
# here because it's OV specific.
self.client.avatar(nick, image, (nx, ny), (bx-BALLOONXOFFSET, by))
def cmd_PING(self, line):
self.write('PONG\r\n')
def cmd_URL(self, line):
cmd, nick, text = line.split(' ', 2)
self.client.url(decode(nick), text)
def cmd_NEW(self, line):
cmd, nick, x, y, filename, nx, ny, size, bx, by = line.split()
nick = decode(nick)
x = int(x)
y = int(y)
nx = int(nx)
ny = int(ny)
size = int(size)
bx = int(bx)
by = int(by)
image = self.get_image(filename, size, 'DCCSENDAV',
self.client.avatar_progress,
self.client.avatar_image, (nick,))
if image is None: image = self.client.newimage()
self.client.new_avatar(nick, (x, y), image, (nx, ny),
(bx-BALLOONXOFFSET, by))
def cmd_NOMORE(self, line):
cmd, nick = line.split()
self.client.del_avatar(decode(nick))
def cmd_EXIT_OBJ(self, line):
cmd, name, x1, y1, x2, y2, duration, host, port = line.split()
self.client.exit_obj(decode(name), host, int(port))
def cmd_DCCGETAV(self, line):
cmd, port, filename, size = line.split()
port = int(port)
size = int(size)
DCCGet(self.host, port, filename, size,
self.progress_callback, self.image_callback)
def cmd_DCCGETROOM(self, line): return self.cmd_DCCGETAV(line)
def cmd_DCCGETOB(self, line): return self.cmd_DCCGETAV(line)
def cmd_DCCSENDAV(self, line):
cmd, port, filename = line.split()
port = int(port)
filename = os.path.join(IMAGEDIR, os.path.basename(filename))
try:
size = os.stat(filename)[stat.ST_SIZE]
DCCSendPassive(self.host, port, filename, size, None, None)
except IOError, info: self.debug(info)
def cmd_ROOMNAME(self, line):
cmd, name = line.split(' ', 1)
self.client.set_title(decode(name))
def cmd_MOUSEOVER(self, line):
cmd, name, x, y, image1, size1, image2, size2, flag = line.split()
name = decode(name)
x = int(x)
y = int(y)
size1 = int(size1)
size2 = int(size2)
flag = int(flag)
image1 = self.get_image(image1, size1, 'DCCSENDOB', None,
self.client.mouseover_image1, (name,))
image2 = self.get_image(image2, size2, 'DCCSENDOB', None,
self.client.mouseover_image2, (name,))
if image1 is None: image1 = self.client.newimage()
if image2 is None: image2 = self.client.newimage()
self.client.mouseover(name, (x, y), image1, image2)
def cmd_SUB(self, line):
self.client.debug('TODO: Implement SUB')
def cmd_WHOIS(self, line):
cmd, nick, text = line.split(' ', 2)
nick = decode(nick)
text = decode(text)
self.client.chat(nick, '*%s* is %s' % (nick, text))
def cmd_PUSH(self, line):
cmd, x, y, speed = line.split()
self.client.push(int(x), int(y), int(speed))
def unknown(self, line):
self.client.debug('Unknown: %s' % line)
def poll():
asyncore.poll()