Exemplo n.º 1
0
class SignupPage(athena.LivePage):
    addSlash = 1
    docFactory = loaders.xmlfile(RESOURCE('templates/signup.xhtml'))

    def render_signup(self, ctx, data):
        pageURL = flat.flatten(url.URL.fromContext(ctx))
        signup = Signup(pageURL)
        signup.setFragmentParent(self)
        return signup

    def renderHTTP(self, ctx):
        """
        Check for a confirmation being used from the email link.

        If the user is confirming a login, move the password from
        unconfirmedPassword into password, thus enabling the account.
        """
        req = inevow.IRequest(ctx)
        confirm = req.args.get('confirm', None)
        if confirm is not None:
            confirm = unicode(confirm[0])
            store = theGlobal['database']
            u = store.findFirst(data.User,
                                data.User.confirmationKey == confirm)
            if u is not None:
                u.password = u.unconfirmedPassword
                u.unconfirmedPassword = None
                req.args.clear()
                self.docFactory = loaders.xmlfile(
                    RESOURCE('templates/confirmed.xhtml'))
            else:
                self.docFactory = loaders.stan(
                    ['Could not confirm that account.'])
        return super(SignupPage, self).renderHTTP(ctx)
Exemplo n.º 2
0
class MapWidget(athena.LiveElement):
    implements(iwebby.IMapWidget)
    jsClass = u'SVGMap.MapWidget'
    docFactory = loaders.xmlfile(RESOURCE('elements/MapWidget'))

    def __init__(self, channel, chatEntry, *a, **kw):
        super(MapWidget, self).__init__(*a, **kw)
        self.channel = channel
        self.chatEntry = chatEntry

    def setMapBackgroundFromChannel(self):
        """
        Create a new BackgroundImage widget and send it to the channel.
        """
        image = BackgroundImage(self.channel)
        image.setFragmentParent(self)
        return self.callRemote("setMapBackground", image)

    def updateObscurementFromChannel(self):
        """
        Just send a new md5key for the obscurement, which the client-side
        widget will use to reset the background.
        """
        return self.callRemote("updateObscurement",
                               self.channel.obscurement.md5)

    def sendCommand(self, command):
        return self.chatEntry.chatMessage(command)

    athena.expose(sendCommand)
Exemplo n.º 3
0
class TextArea(util.RenderWaitLiveElement):
    """A scrollable widget that displays mostly lines of text."""
    implements(iwebby.ITextArea)  # FIXME - move to here
    jsClass = u"StainedGlass.TextArea"
    docFactory = loaders.xmlfile(RESOURCE('elements/TextArea'))
    widgetArgs = None

    def __init__(self, *a, **kw):
        super(TextArea, self).__init__(*a, **kw)
        self.messageQueue = []

    def printClean(self, message):
        """
        Send text to the widget if it is ready; if not, queue text
        for later.
        """
        message = util.flattenMessageString(message)
        return self.callRemote('appendTo', message)

    def setInitialArguments(self, *a, **kw):  # FIXME - raped from tabs.py
        assert len(kw) == 0, "Cannot pass keyword arguments to a Widget"
        self.widgetArgs = a

    def getInitialArguments(self):  # FIXME - raped from tabs.py
        args = ()
        if self.widgetArgs is not None:
            args = self.widgetArgs

        return args
Exemplo n.º 4
0
class TabsElement(athena.LiveElement):
    docFactory = loaders.xmlfile(RESOURCE('elements/Tabs'))
    jsClass = u"Tabby.TabsElement"
    widgetArgs = EMPTY

    def addTab(self, id, label):
        return self.callRemote('addTab', id, label)

    def setTabBody(self, id, content):
        return self.callRemote('setTabBody', id, content)

    def removeTab(self, id):
        return self.callRemote('removeTab', id)

    def addInitialTab(self, id, label, content):
        """
        Specify a tab that will be sent in the initial arguments.
        Has no effect after the first time this widget gets rendered.
        """
        assert getattr(self, '_athenaID', None) is None, (
            "Cannot call this after the widget has rendered.")
        if self.widgetArgs is EMPTY:
            self.widgetArgs = []
        self.widgetArgs.append((id, label, content))

    def getInitialArguments(self):
        return self.widgetArgs
Exemplo n.º 5
0
class FileChooser(athena.LiveElement):
    implements(IFileObserver)
    jsClass = u'WebbyVellum.FileChooser'
    docFactory = loaders.xmlfile(RESOURCE('elements/FileChooser'))

    def __init__(self, user, *a, **kw):
        super(FileChooser, self).__init__(*a, **kw)
        self.user = user

    def chooser(self, req, tag):
        # get file notifications from the user. set this as early as possible
        # in the render (which is here)
        self.user.addObserver(self)
        self.page.notifyOnDisconnect().addBoth(
            lambda reason: self.user.removeObserver(self))

        return tag[self._getIconsFromDatabase()]

    page.renderer(chooser)

    def _getIconsFromDatabase(self):
        """
        @return a list of the choosericons, pre-rendering.
        """
        db = theGlobal['database']
        _fileitems = db.query(data.FileMeta,
                              data.FileMeta.user == self.user,
                              sort=data.FileMeta.filename.ascending)
        ret = [self._newIconFromItem(fi) for fi in _fileitems]
        return ret

    def _newIconFromItem(self, fileitem):
        ch = ChooserIcon(self.user, fileitem)
        ch.setFragmentParent(self)
        return ch

    def fileAdded(self, fileitem):
        """
        Construct a new icon and send it to the browser
        """
        icon = self._newIconFromItem(fileitem)
        return self.callRemote('fileAdded', icon)

    def fileRemoved(self, fileitem):
        """
        Get a reference to the LiveElement for that item, and
        make a .remove call on it directly
        """
        TODO
        return eelf.fileItems[fileitem].remove(FOO)

    def fileModified(self, fileitem):
        """
        Get a reference to the LiveElement for that item, and
        make a .modify call in it.
        TODO Which parts need to be sent?
        """
        TODO
        return self.fileItems[fileitem].modify(FOO)
Exemplo n.º 6
0
class AccountManager(athena.LiveElement):
    """
    UI element that handles changing of the nick and processing a login to the
    IRC server.
    """
    jsClass = u"WebbyVellum.AccountManager"
    docFactory = loaders.xmlfile(RESOURCE('elements/AccountManager'))
    implements(iwebby.IChatAccountManager)

    def __init__(self, accountManager, conversationTabs, user, *a, **kw):
        super(AccountManager, self).__init__(*a, **kw)
        self.accountManager = accountManager
        self.conversationTabs = conversationTabs
        self.user = user

    def getInitialArguments(self):
        nick = self.user.nick
        channels = self.user.recentChannels
        if nick and channels:
            # when both nick and channels are already set, log the irc user
            # straight in.
            self.onLogOnSubmit(nick, channels)
            # set autoHide = True on the widget because login will be
            # automatic
            return (nick, channels, True)
        return (nick, channels)

    def onLogOnSubmit(self, nick, channels):
        """
        Process by looking up the password for that nick and starting
        a login process on the IRC protocol.
        """
        host = 'localhost'.encode('utf8')
        password = self.user.password.encode('utf8')
        username = nick.encode('utf8')

        # SET the permanent nick to the nick we were provided, trust it.
        # We can trust the nick because use of AccountManager 
        # implies that the user is *already* authenticated, through
        # the web.  Users visiting using a regular IRC client will 
        # naturally have to supply their passwords the normal way.
        self.user.nick = unicode(nick)

        channels = channels.encode('utf8')
        d = self.accountManager.doConnection(host, username, password, channels)

        def _gotAccount(acct):
            # set up disconnection callback for browser close etc.
            def logOff(_ignored, acct):
                self.accountManager.disconnectAsNeeded(acct)
            self.page.notifyOnDisconnect().addBoth(logOff, acct)

            return u'connected %s@%s and joined %s' % (username, host, channels)

        d.addCallback(_gotAccount)
        return d
    athena.expose(onLogOnSubmit)
Exemplo n.º 7
0
class TopicBar(util.RenderWaitLiveElement):
    """
    Text widget that sits above the channel window in a tab and displays the
    current channel topic.
    """
    implements(iwebby.ITopicBar)
    docFactory = loaders.xmlfile(RESOURCE('elements/TopicBar'))
    jsClass = u'WebbyVellum.TopicBar'

    def setTopic(self, topic):
        topic = unicode(topic)
        return self.callRemote('setTopic', topic)
Exemplo n.º 8
0
class Enclosure(athena.LiveElement):
    """
    @ivar windowTitle: A title that will be shown at the top of the widget
    @ivar userClass: additional text to add at the end of the class=".." attribute
    @ivar decorated: if False, no decorations (border, titlebar, etc.)
    """
    jsClass = u"StainedGlass.Enclosure"
    docFactory = loaders.xmlfile(RESOURCE('elements/Enclosure'))

    def __init__(self,
                 windowTitle='~',
                 userClass='',
                 decorated=True,
                 *a,
                 **kw):
        super(Enclosure, self).__init__(*a, **kw)
        self.windowTitleStan = windowTitle
        self.userClassStan = userClass
        self.children = []
        self.decorated = decorated

    def enclosedRegion(self, request, tag):
        return tag[self.children]

    athena.renderer(enclosedRegion)

    def __getitem__(self, children):
        self.children = children
        return self

    def extraClasses(self, request, tag):
        tag.fillSlots('userClass', self.userClassStan)
        if self.decorated:
            tag.fillSlots('decorationClass', 'decorated')
        else:
            tag.fillSlots('decorationClass', '')
        return tag

    athena.renderer(extraClasses)

    def windowTitle(self, request, tag):
        tag.fillSlots('windowTitle', self.windowTitleStan)
        return tag

    athena.renderer(windowTitle)

    def decorations(self, req, tag):
        if self.decorated:
            return tag
        return []

    athena.renderer(decorations)
Exemplo n.º 9
0
class Signup(athena.LiveElement):
    docFactory = loaders.xmlfile(RESOURCE('elements/Signup'))
    jsClass = u'Signup.Signup'

    def __init__(self, pageURL, *a, **kw):
        super(Signup, self).__init__(*a, **kw)
        self.pageURL = pageURL

    def processSignup(self, email, password):
        key = util.label()
        store = theGlobal['database']
        u = store.findFirst(data.User, data.User.email == email)

        if u is None:
            u = data.User(store=store,
                          email=email,
                          password=None,
                          unconfirmedPassword=password,
                          confirmationKey=key)
        else:
            u.unconfirmedPassword = password
            u.confirmationKey = key

        link = self.pageURL + '?confirm=%s' % (key, )

        d = sendEmail(
            email,
            "Confirm Vellum Signup",  ## {{{
            """
You signed up to use Vellum, I think.

Confirm your signup on the Vellum website by clicking on the following link:
%s
""" % (link, ))  ## }}}

        d.addCallback(lambda response: unicode(response))

        def _didntSendEmail(failure):
            """Remove the new user object if we can't even send the email"""
            u.deleteFromStore()
            return failure

        d.addErrback(_didntSendEmail)

        return d

    athena.expose(processSignup)
Exemplo n.º 10
0
class ChannelLayout(page.Element):
    docFactory = loaders.xmlfile(RESOURCE('elements/ChannelLayout'))
    def __init__(self, controlBar, topicBar, mapDiv, channelDiv, chatEntry):
        self.controlBar = controlBar
        self.topicBar = topicBar
        self.mapDiv = mapDiv
        self.channelDiv = channelDiv
        self.chatEntry = chatEntry

    def layout(self, req, tag):
        tag.fillSlots('controlBar', self.controlBar)
        tag.fillSlots('topicBar', self.topicBar)
        tag.fillSlots('mapDiv', self.mapDiv)
        tag.fillSlots('channelDiv', self.channelDiv)
        tag.fillSlots('chatEntry', self.chatEntry)
        return tag

    page.renderer(layout)
Exemplo n.º 11
0
class IRCPage(athena.LivePage):
    """
    Page container for the IRC UI and the maps UI
    """
    addSlash = True

    docFactory = loaders.xmlfile(RESOURCE('templates/webby.xhtml'))

    def renderHTTP(self, ctx):
        """Set XHTML as the mime type so SVG can be rendered."""
        req = inevow.IRequest(ctx)
        req.setHeader('content-type', 'application/xhtml+xml')
        return super(IRCPage, self).renderHTTP(ctx)

    def __init__(self, *a, **kw):
        super(IRCPage, self).__init__(*a, **kw)
        self.chatui = minchat.MinChat()

    def render_chat(self, ctx, _):
        accountManager = minchat.MinAccountManager(self.chatui)
        ss = inevow.ISession(ctx)
        irc = IRCContainer(accountManager, ss.user)
        irc.setFragmentParent(self)

        self.chatui.initUI(irc)

        return ctx.tag[irc]

    def render_gmtools(self, ctx, _):
        ss = inevow.ISession(ctx)
        gmt = gmtools.GMTools(ss.user)
        enc = stainedglass.Enclosure(windowTitle="GM Tools", 
                userClass="gmtools draggable")
        enc.setFragmentParent(self)
        gmt.setFragmentParent(enc)

        return ctx.tag[enc[gmt]]

    def render_topLinks(self, ctx, _):
        ss = inevow.ISession(ctx)
        ctx.tag.fillSlots('email', ss.user.email)
        ctx.tag.fillSlots('logoutHref', url.root.child(guard.LOGOUT_AVATAR))
        ctx.tag.fillSlots('adminLink', T.a['Admin (TODO)'])
        return ctx.tag
Exemplo n.º 12
0
class BackgroundImage(athena.LiveElement):
    ## jsClass = u'SVGMap.BackgroundImage'
    docFactory = loaders.xmlfile(RESOURCE('elements/BackgroundImage'))

    def __init__(self, channel, *a, **kw):
        super(BackgroundImage, self).__init__(*a, **kw)
        self.channel = channel

    def imageLiveElement(self, req, tag):
        ch = self.channel
        href = u'/files/%s' % (ch.background.md5, )
        obscurementHref = u'/files/%s' % (ch.obscurement.md5, )
        tag.fillSlots('width', ch.background.width)
        tag.fillSlots('height', ch.background.height)
        tag.fillSlots('href', href)
        tag.fillSlots('obscurementHref', obscurementHref)
        return tag(render=T.directive("liveElement"))

    athena.renderer(imageLiveElement)
Exemplo n.º 13
0
class ChooserIcon(athena.LiveElement):
    docFactory = loaders.xmlfile(RESOURCE('elements/ChooserIcon'))
    jsClass = u'WebbyVellum.ChooserIcon'

    def __init__(self, user, fileitem, *a, **kw):
        super(ChooserIcon, self).__init__(*a, **kw)
        self.fileitem = fileitem
        self.user = user

    def chooserIcon(self, req, tag):
        fi = self.fileitem
        tag.fillSlots('filename', fi.filename)
        if fi.mimeType.startswith(u'image/') and fi.thumbnail is not None:
            tag.fillSlots('icon', '/files/%s/thumb' % (fi.md5, ))
        else:
            tag.fillSlots('icon',
                          '/static/%s' % (iconForMimeType(fi.mimeType), ))
        return tag

    page.renderer(chooserIcon)
Exemplo n.º 14
0
class NameSelect(util.RenderWaitLiveElement):
    """
    <select> box that contains the list of names for a group conversation.
    """
    implements(iwebby.INameSelect)
    jsClass = u'WebbyVellum.NameSelect'
    docFactory = loaders.xmlfile(RESOURCE('elements/NameSelect'))

    def addName(self, name, flags):
        # TODO - parse flags
        name = unicode(name)
        return self.callRemote('addName', name, None)

    def removeName(self, name):
        name = unicode(name)
        return self.callRemote('removeName', name)

    def setNames(self, names):
        # TODO - flags?
        return self.callRemote('setNames', map(unicode, names))
Exemplo n.º 15
0
    class LoginPage(StaticRoot):
        """Page which asks for username/password."""
        addSlash = True
        docFactory = loaders.xmlfile(RESOURCE('templates/login.xhtml'))

        def child_game(self, ctx,):
            """Redirect to the login page when you attempt to return to /game"""
            return url.root.child('')
     
        def render_form(self, ctx, data):
            req = inevow.IRequest(ctx)
            if 'login-failure' in req.args:
                ctx.tag.fillSlots('loginStatus', ctx.tag.onePattern('unauthorized'))
            else:
                ctx.tag.fillSlots('loginStatus', [])

            ctx.tag.fillSlots('action', guard.LOGIN_AVATAR)
            return ctx.tag
     
        def logout(self):
            print "Bye"
Exemplo n.º 16
0
    def renderHTTP(self, ctx):
        """
        Check for a confirmation being used from the email link.

        If the user is confirming a login, move the password from
        unconfirmedPassword into password, thus enabling the account.
        """
        req = inevow.IRequest(ctx)
        confirm = req.args.get('confirm', None)
        if confirm is not None:
            confirm = unicode(confirm[0])
            store = theGlobal['database']
            u = store.findFirst(data.User,
                                data.User.confirmationKey == confirm)
            if u is not None:
                u.password = u.unconfirmedPassword
                u.unconfirmedPassword = None
                req.args.clear()
                self.docFactory = loaders.xmlfile(
                    RESOURCE('templates/confirmed.xhtml'))
            else:
                self.docFactory = loaders.stan(
                    ['Could not confirm that account.'])
        return super(SignupPage, self).renderHTTP(ctx)
Exemplo n.º 17
0
class UploadPage(formal.ResourceMixin, rend.Page):
    """
    Perform file uploads which will be stored in the Axiom store.
    """
    addSlash = True
    docFactory = loaders.xmlfile(RESOURCE('templates/upload.xhtml'))

    def __init__(self, user, *a, **kw):
        formal.ResourceMixin.__init__(self, *a, **kw)
        rend.Page.__init__(self, *a, **kw)
        self.user = user

    def form_upload(self, ctx):
        f = formal.Form()
        f.addField('file', formal.File())
        f.addAction(self.saveFile, name="submit", label="Upload File")
        return f

    def saveFile(self, ctx, form, data):
        """Receive the file and drop it into the Axiom box."""
        db = theGlobal['database']
        filename, filedata = data['file']

        # when the file was not successfully uploaded, filename==u''
        if len(filename) > 0:
            readdata = filedata.read()
            m = unicode(md5.md5(readdata).hexdigest())
            # make sure that a particular file can only be uploaded once
            if db.findFirst(FileMeta, FileMeta.md5 == m) is None:
                # so we really did get a file upload

                def txn():
                    mimeType = unicode(mimetypes.guess_type(filename)[0])
                    newFileData = FileData(store=db, data=readdata)
                    newfile = FileMeta(
                        store=db,
                        filename=filename,
                        data=newFileData,
                        md5=m,
                        mimeType=mimeType,
                        user=self.user,
                    )

                    # now thumbnail it and store image metas
                    if mimeType.startswith('image/'):
                        # PIL can't thumbnail every identifiable kind of
                        # image, so just punt if it fails to update.
                        try:
                            filedata.seek(0)
                            original = Image.open(filedata)
                            thumb = original.copy()
                            thumb.thumbnail((48, 48), Image.ANTIALIAS)
                            _tempfile = StringIO()
                            thumb.save(_tempfile, 'PNG', optimize=True)
                            _tempfile.seek(0)
                            newThumbData = FileData(store=db,
                                                    data=_tempfile.read())
                            newfile.thumbnail = newThumbData

                            newfile.width, newfile.height = original.size

                        except IOError:
                            pass

                    # notify the user, so the user can notify observers that
                    # the file list has changed.
                    self.user.fileAdded(newfile)

                db.transact(txn)

        return UploadDone()
Exemplo n.º 18
0
 def child_static(self, ctx):
     return static.File(RESOURCE('static'))
Exemplo n.º 19
0
import os

from nevow import athena

from webby import RESOURCE

webbyPkg = athena.AutoJSPackage(RESOURCE('static'))
Exemplo n.º 20
0
class ControlBar(page.Element):
    docFactory = loaders.xmlfile(RESOURCE('elements/ControlBar'))
Exemplo n.º 21
0
from twisted.python import log

from zope.interface import implements

from nevow import loaders, athena, rend, inevow, url, tags as T, flat, page

from webby import tabs, util, theGlobal, data, RESOURCE
from webby.data import FileMeta, FileData
from webby.iwebby import IFileObserver

import formal

# load our own mime-types, because the set python comes with by default is
# *pathetic*
mimetypes.init([
    RESOURCE('mime.types'),
])

# map MIME types to image filename. This is a list because the
# matches are meant to be done in order.  Thus, the most generic
# icon is the last one.
mimeIcons = [
    ('image/.*', 'image-x-generic.png'),  # {{{
    ('audio/.*', 'audio-x-generic.png'),
    ('video/.*', 'video-x-generic.png'),
    ('text/x-.*sh', 'application-x-executable.png'),
    ('text/.*', 'text-x-generic.png'),
    ('application/java-archive', 'application-x-executable.png'),
    ('application/x-msdos-program', 'application-x-executable.png'),
    ('application/x-msi', 'application-x-executable.png'),
    ('application/zip', 'package-x-generic.png'),
Exemplo n.º 22
0
class UploadDone(rend.Page):
    docFactory = loaders.xmlfile(RESOURCE('templates/uploaddone.xhtml'))
Exemplo n.º 23
0
class ChatEntry(athena.LiveElement):
    docFactory = loaders.xmlfile(RESOURCE('elements/ChatEntry'))
    jsClass = u"WebbyVellum.ChatEntry"

    def __init__(self, conversation, *a, **kw):
        super(ChatEntry, self).__init__(*a, **kw)
        self.conversation = conversation

    def chatMessage(self, message):
        conv = self.conversation

        parsed = parseirc.line.parseString(message)
        if parsed.command:

            def irccmdFallback(message, conv):
                strCommand = parsed.commandWord.encode('utf8').upper()
                message = '%s %s' % (strCommand, message)
                return self.irccmd_raw(message, conv)
                
            m = getattr(self, 
                        'irccmd_%s' % (parsed.commandWord,),
                        irccmdFallback)
            m(parsed.commandArgs.encode('utf8'), conv)
        else:
            self.say(parsed.nonCommand[0].encode('utf8'), conv)

        return u'ok'

    athena.expose(chatMessage)

    def say(self, message, conv):
        return conv.sendText(message)

    def irccmd_me(self, args, conv):
        return conv.sendText(args, metadata={'style':'emote'})

    def irccmd_join(self, args, conv):
        groups = args.split()

        ## acct = conv.group.account
        # We're using this way to get the account because I can't figure out a
        # way to make it so all conversations have access to the account.  I
        # don't know if this will work.  FIXME
        acct = self.page.chatui.onlineClients[0].account

        if groups:
            args = args[len(groups[0])-1:].lstrip()
            groups = groups[0].split(',')

        acct.joinGroups(groups)

    irccmd_j = irccmd_join

    def irccmd_topic(self, args, conv):
        client = self.page.chatui.onlineClients[0]

        channel = None

        # see if the user is trying to see/set the topic for some other 
        # channel.  This applies if the first word begins with a hash #.
        if args.strip() != '':
            firstArg = args.split()[0]
            if firstArg[0] == '#':
                # remove the channel.
                args = args[len(firstArg):]
                args = args.lstrip()
                channel = firstArg

        if channel is None:
            if hasattr(conv, 'group'):
                channel = conv.group.name
            else:
                return conv.sendText("Cannot set or view topic of SERVER tab")

        channel = channel.lstrip('#')

        if args.strip() == '':
            args = None
        
        client.topic(channel, args)

    def irccmd_part(self, args, conv):
        groups = args.split()

        ## acct = conv.group.account
        # We're using this way to get the account because I can't figure out a
        # way to make it so all conversations have access to the account.  I
        # don't know if this will work.
        acct = self.page.chatui.onlineClients[0].account

        if groups:
            args = args[len(groups[0])-1:].lstrip()
            groups = groups[0].split(',')
        else:
            if hasattr(conv, 'group'):
                groups.append(conv.group.name.lstrip('#'))
            else:
                return conv.sendText("Cannot /part from SERVER tab")

        # TODO: Find out how to support parting messages
        acct.leaveGroups(groups)

    irccmd_leave = irccmd_part

    def irccmd_raw(self, args, conv):
        ## acct = conv.group.account
        # We're using this way to get the account because I can't figure out a
        # way to make it so all conversations have access to the account.  I
        # don't know if this will work.
        acct = self.page.chatui.onlineClients[0].account
        return acct.client.sendLine(args)

    def irccmd_notice(self, args, conv):
        client = self.page.chatui.onlineClients[0]
        recipient, rest = args.split(None, 1)
        r = client.notice(recipient, rest)
        iwebby.ITextArea(conv).printClean(u'>%s< %s' % (recipient, rest))
        return r

    def irccmd_query(self, args, conv):
        try:
            personName = args.split()[0]
            mesg = args[len(personName):].lstrip()
            chatui = self.page.chatui
            client = chatui.onlineClients[0]
            newConv = chatui.getConversation(chatui.getPerson(personName, client))
            newConv.show()
            if mesg:
                newConv.sendText(mesg)
        except:
                conv.sendText("Problems with /query, bailing out.")