Creating Plugins for Dunking Parrot Chat


Dunking Parrot Chat allows you to write "plugins" to enhance the functionality of the product.  A plugin is a standalone program that can be launched from the chat client, and that returns data to the client to be displayed in the main chat window.  A plugin could be written to go out to the web and look up, for example, stock quotes, the weather, or headline news.  This tutorial describes how to create a simple plugin using Microsoft Visual Basic™ 6.0

How Plugins Are Launched
When a chat user decides to execute a plugin, he types a backslash ("\"), followed by the name of the plugin, then a blank space, then any parameters that he wishes to pass to the plugin. The chat client will then create a text file that has the same name as the plugin. This text file contains the port number that the client receives UDP data on, as well as the parameters that the user wishes to pass to the plugin.  In this tutorial, we will assume that we are writing a plugin named "stock" that will go out to the web and retrieve stock quotes.  Let's assume that the user wants to execute the stock plugin to look up the stocks for Red Hat (symbol RHAT) and Microsoft (symbol MSFT).  Let us also assume that the user is using port 3209 to receive UDP data in the chat client.  To launch this plugin, the user would type:

\stock rhat msft

The chat client will create a text file named "stock.txt", and will attempt to launch "stock.exe" from the chat directory.   The text file will contain two lines, which look like this:

Port=3209
Data=rhat msft

It is the responsibility of the plugin to parse this text file and properly handle the data read therein. 

Getting Started Writing a Plugin
Create a Visual Basic project with a single form (frmMain in our example).   The VISIBILE property of this form should be set to FALSE so that the form does not show up while the plugin is running.  Alternatively, you could set the property to TRUE and use the form to display progress information while your plugin is running.   It's up to you.  You should also place a Microsoft Winsock Control on the form.  In the example below, I have named that control "udpSocket".

In your project, insert a module.  I call this module "InternetDownload.bas".  It contains the header information and dll declares necessary to do internet calls using the WININET dll file that is included with Microsoft Internet Explorer.  This allows you to code plugins without having to use the Internet Transfer Control.  The code of the module should look like this:

Option Explicit
' This module handles the internet calls required to perform Internet
' transfers without using the Microsoft Internet Transfer Control.


Public Const INTERNET_OPEN_TYPE_PRECONFIG = 0
Public Const INTERNET_OPEN_TYPE_DIRECT = 1
Public Const INTERNET_OPEN_TYPE_PROXY = 3

Public Const scUserAgent = "VB OpenUrl"
Public Const INTERNET_FLAG_RELOAD = &H80000000

Public Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
(ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
ByVal sProxyBypass As String, ByVal lFlags As Long) As Long

Public Declare Function
InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" _
(ByVal
hOpen As Long, ByVal sUrl As String, ByVal sHeaders As String, _
ByVal
lLength As Long, ByVal lFlags As Long, ByVal lContext As Long) As Long

Public Declare Function InternetReadFile Lib "wininet.dll" _
(ByVal
hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, _
lNumberOfBytesRead As Long) As Integer

Public Declare Function InternetCloseHandle Lib "wininet.dll" _
(ByVal
hInet As Long) As Integer

Heading over to frmMain, you should declare a global variable, messageTab, as String.  This will be used to determine which tab to route the plugin out put to on the chat window.

In the form_load() event of frmMain, you should call a routine to read and parse the input file.  This is the first thing that occurs in my plugins, and the code to do that looks like this:


Public Sub Read_Input_File()
' Read the "stock.txt" file created by the chat server. This file tells us what port
' to return data to the chat client on as well as which stock symbol(s) to look up.


    On Error GoTo error_handler

    Dim fileNum As Integer
    Dim index As Integer
    Dim fileName As String
    Dim inString As String
    Dim itemValue As String
    Dim itemName As String

    fileName = App.Path & "\stock.txt"
    fileNum = FreeFile()

    Open fileName For Input As fileNum

    Do While Not EOF(fileNum)
        Input #fileNum, inString
        If inString = "" Then
            ' Skip the txt file header and any blank lines
        Else
            For index = 1 To Len(inString)
                If Mid$(inString, index, 1) = "=" Then
                    itemValue = Mid$(inString, index + 1, (Len(inString) - index))
                    itemName = Mid$(inString, 1, (index - 1))
                End If
            Next index
            Set_INI_Variable itemName, itemValue
        End If
    Loop

    Close
#fileNum

    Exit Sub

error_handler:
    Send_UDP_Message "Stock plugin encountered error " & Err.Description, False
    Send_UDP_Message "      ",True

    End

End Sub


Private Sub
Set_INI_Variable(itemName As String, itemValue As String)
' Here we assign the INI file fields to the variables that they represent
    On Error GoTo error_handler

    Dim index As Integer
    Dim endPos As Integer
    Dim startPos As Integer

    itemName = UCase(itemName) ' Convert variable name to uppercase

    Select Case itemName

        Case "DATA"
' Parse your data line here!!
        Case "PORT"
            returnPort = Val(itemValue)
        Case "TAB"
            messageTab = itemValue
    End Select

    Exit Sub


error_handler:
    Send_UDP_Message "Stock plugin encountered error " & Err.Description, False
    Send_UDP_Message "      ",True

    End

End Sub

As you can see, we have a couple of places in the code above where we call a routine called "Send_UDP_Message".  This is the routine that we use to talk to the chat client.  by calling this routine, you can send any message to the client that you want.  If you wanted to send a message to the client when your plugin was first launched, you could code something like: Send_UDP_Message "Stock plugin launched!".     The code for the Send_UDP_Message routine looks like this:


Private Sub Send_UDP_Message(messageData As String, resetMsg As Boolean)
' Send routine for communicating with the chat client


    Dim outMessageType, outMessageName, outMessageLen, outMessageData As String

    On Error Resume Next

    With udpSocket
        .Close
        .RemoteHost = "127.0.0.1"  ' This IP always refers to the local machine
        .RemotePort = returnPort   ' Bind to return port that the client supplied us
        .Bind 3299                  ' 3299 is a randomly chosen port
    End With

' Do not leave out the trailing spaces below. The chat client expects to get its
' messages in a specific format.  Message Type must be 12 bytes in length.   There are
' two possible message types sent to the client.  Type "SERVER" causes the client to
' display the incoming data as a server message.  Type "RESET" tells the client to reset
' the socket to accept messages from the real chat server once again.

    outMessageType = "SERVER      "
    If resetMsg = True Then
outMessageName = "RESET       "
    Else
outMessageName = Left("MESSAGE" & messageTab & " ", 16)
    End If
' Message name must be 16 bytes in length. You could also use RESPONSE instead
' of MESSAGE. Responses are in a proportional font, and Messages are in the default
' chat font.

    outMessageName = "MESSAGE          "

    outMessageLen = "0000" & Len(messageData)
    outMessageLen = Right(outMessageLen, 4)

    outMessageData = outMessageType & outMessageName & outMessageLen & messageData
    udpSocket.SendData outMessageData

End Sub

These are the main routines that you need to code to parse the input text file and communicate with the client.  To retrieve a webpage, you might code something like what is shown below.  The example below is a routine called Read_URL.  It will read the webpage of your choice and place the entire HTML code into a string variable which you can then parse.  You must pass the URL to this routine.  You can either hardcode the URL or build it on the fly including variables from the data that the chat client user sent to the plugin.  To call the routine, you could do something like this:  Read_URL "http://www.thecharlotteworld.com" 


Private Sub Read_URL(stockUrl As String, symbol As String)

    Dim hOpen As Long
    Dim hOpenUrl As Long
    Dim bDoLoop As Boolean
    Dim bRet As Boolean
    Dim sReadBuffer As String * 2048
    Dim lNumberOfBytesRead As Long
    Dim totalBytes As Long
    Dim sBuffer As String
    Dim strLaunch As String

    totalBytes = 0

    hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString,     vbNullString, 0)
    hOpenUrl = InternetOpenUrl(hOpen, stockUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)

    bDoLoop = True
    While bDoLoop
        sReadBuffer = vbNullString
        bRet = InternetReadFile(hOpenUrl, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
        sBuffer = sBuffer & Left$(sReadBuffer, lNumberOfBytesRead)
        totalBytes = totalBytes + lNumberOfBytesRead
        DoEvents
        If Not CBool(lNumberOfBytesRead) Then bDoLoop = False
    Wend

    If hOpenUrl <> 0 Then InternetCloseHandle (hOpenUrl)
    If hOpen <> 0 Then InternetCloseHandle (hOpen)

End Sub

Compiling Your Plugin
Once you have coded your plugin and are ready to compile it, there is one final thing that you must do.  The Chat Client recognizes plugins by scanning every ".exe" file in the path that it was loaded from.  The client checks the version ("App.Minor") of each .exe file, and if a file has a version of "99" then chat recognizes it as a plugin.  When compiling your plugin, make sure that you select Options from the Compile window and set the minor version of your application to "99".