Pythonism

code and the oracular

How To Make A Simple Skype Bot With Python

leave a comment »

Background

AIML is a chatbot framework invented by Dr Richard Wallace. Sites like Pandorabots allow users to make their own bots from this technology. That’s the easy route if you want it….

Here we will take a simple DIY approach to making a Skype Bot ourselves that is powered by the ALICE bot AIML set. Instead of being hosted by someone else though we can make it run from a local machine using the Skype4Py module that provides an interface to the conventional Skype desktop client, in my case the one provided for Linux Mint, although this should work on any system that has the Skype client, other Linuxes, Mac or Windows. We also need PyAIML which is easy to get using pip, and the ALICE Standard AIML set which google can help you find.

Preliminary Bits

I installed Skype4Py with pip

$ sudo pip install Skype4Py

I chose to make a new Skype account for the bot, noticing that Skype does allow this even if you use the same email address as your existing account but simply give a different username.

So having created the account I activated the desktop client and signed in, then I was ready to start playing with my code.

Getting Python to talk to Skype

The first thing to do is:

import Skype4Py

then create a skype connection with

skype = Skype4Py.Skype()
skype.Attach()

The object “skype” has a selection of methods associated with it which we can explore by doing

dir(skype)

giving

['ActiveCalls', 'ActiveChats', 'ActiveFileTransfers', 'ApiSecurityContextEnabled', 'ApiWrapperVersion', 'Application', 'AsyncSearchUsers', 'Attach', 'AttachmentStatus', 'BookmarkedChats', 'Cache', 'Call', 'Calls', 'ChangeUserStatus', 'Chat', 'Chats', 'ClearCallHistory', 'ClearChatHistory', 'ClearVoicemailHistory', 'Client', 'Command', 'CommandId', 'Conference', 'Conferences', 'ConnectionStatus', 'Convert', 'CreateChatUsingBlob', 'CreateChatWith', 'CreateGroup', 'CreateSms', 'CurrentUser', 'CurrentUserHandle', 'CurrentUserProfile', 'CurrentUserStatus', 'CustomGroups', 'DeleteGroup', 'EnableApiSecurityContext', 'FileTransfers', 'FindChatUsingBlob', 'FocusedContacts', 'FriendlyName', 'Friends', 'Greeting', 'Groups', 'HardwiredGroups', 'Message', 'Messages', 'MissedCalls', 'MissedChats', 'MissedMessages', 'MissedSmss', 'MissedVoicemails', 'Mute', 'OnApplicationConnecting', 'OnApplicationDatagram', 'OnApplicationReceiving', 'OnApplicationSending', 'OnApplicationStreams', 'OnAsyncSearchUsersFinished', 'OnAttachmentStatus', 'OnAutoAway', 'OnCallDtmfReceived', 'OnCallHistory', 'OnCallInputStatusChanged', 'OnCallSeenStatusChanged', 'OnCallStatus', 'OnCallTransferStatusChanged', 'OnCallVideoReceiveStatusChanged', 'OnCallVideoSendStatusChanged', 'OnCallVideoStatusChanged', 'OnChatMemberRoleChanged', 'OnChatMembersChanged', 'OnChatWindowState', 'OnClientWindowState', 'OnCommand', 'OnConnectionStatus', 'OnContactsFocused', 'OnError', 'OnFileTransferStatusChanged', 'OnGroupDeleted', 'OnGroupExpanded', 'OnGroupUsers', 'OnGroupVisible', 'OnMessageHistory', 'OnMessageStatus', 'OnMute', 'OnNotify', 'OnOnlineStatus', 'OnPluginEventClicked', 'OnPluginMenuItemClicked', 'OnReply', 'OnSilentModeStatusChanged', 'OnSmsMessageStatusChanged', 'OnSmsTargetStatusChanged', 'OnUserAuthorizationRequestReceived', 'OnUserMood', 'OnUserStatus', 'OnVoicemailStatus', 'OnWallpaperChanged', 'PlaceCall', 'PredictiveDialerCountry', 'Privilege', 'Profile', 'Property', 'Protocol', 'RecentChats', 'RegisterEventHandler', 'ResetCache', 'SearchForUsers', 'SendCommand', 'SendMessage', 'SendSms', 'SendVoicemail', 'Settings', 'SilentMode', 'Smss', 'Timeout', 'UnregisterEventHandler', 'User', 'UsersWaitingAuthorization', 'Variable', 'Version', 'Voicemail', 'Voicemails', '_AddEvents', '_Alter', '_Api', '_AsyncSearchUsersReplyHandler', '_Cache', '_CacheDict', '_CallEventHandler', '_ChangeUserStatus_UserStatus', '_Client', '_Convert', '_DefaultEventHandlers', '_DoCommand', '_EventHandlerObject', '_EventHandlers', '_EventHandlingBase__Logger', '_EventNames', '_EventThreads', '_GetActiveCalls', '_GetActiveChats', '_GetActiveFileTransfers', '_GetApiWrapperVersion', '_GetAttachmentStatus', '_GetBookmarkedChats', '_GetCache', '_GetChats', '_GetClient', '_GetCommandId', '_GetConferences', '_GetConnectionStatus', '_GetConvert', '_GetCurrentUser', '_GetCurrentUserHandle', '_GetCurrentUserProfile', '_GetCurrentUserStatus', '_GetCustomGroups', '_GetDefaultEventHandler', '_GetFileTransfers', '_GetFocusedContacts', '_GetFriendlyName', '_GetFriends', '_GetGroups', '_GetHardwiredGroups', '_GetMissedCalls', '_GetMissedChats', '_GetMissedMessages', '_GetMissedSmss', '_GetMissedVoicemails', '_GetMute', '_GetPredictiveDialerCountry', '_GetProtocol', '_GetRecentChats', '_GetSettings', '_GetSilentMode', '_GetSmss', '_GetTimeout', '_GetUsersWaitingAuthorization', '_GetVersion', '_GetVoicemails', '_Logger', '_ObjectCache', '_Profile', '_Property', '_Search', '_SetCache', '_SetCommandId', '_SetCurrentUserStatus', '_SetDefaultEventHandler', '_SetEventHandlerObject', '_SetFriendlyName', '_SetMute', '_SetProtocol', '_SetSilentMode', '_SetTimeout', '_Settings', '_Timeout', '__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Phew !

Often using dir() and looking at methods docstrings which are of the form foo.__doc__ enough can be learned about using a module you haven’t worked with before. Trial and error while testing code in the interpreter is also essential of course.

The above output is just about everything that you need to control the Skype desktop client.

In our case we only need a couple of these

skype.Messages

gives a list of messages in a chat for a given user

skype.SendMessage

sends a single text based message to a given user

Once you have a reply the message object has some data bound to it like the body text and whether it has been marked as read. I’m being sketchy with the details because I can’t always hold your hand 🙂

With these we can implement a very simple bot.

I chose to make each running bot an object which I defined in a class named just Bot()

Persistence Across Multiple Sessions

Each Bot has its own aiml kernel running so that each user gets a personalised ALICE which remembers some of what it has chatted about using AIML Predicates, from last time they interacted.

For each user in a list, a bot is paired to them, and at each tick messages are polled for and responses generated for each instance.

If it is the first time with a user then there is no saved brain, so ALICE is loaded from the AIML set by its bootstraps 🙂 this is done with

kernel.bootstrap(learnFiles="std-startup.xml", commands="load aiml b")

(assuming that the ALICE files are in current folder)

at the end of a chat, designated as when a user says ‘bye’, the ALICE brain is saved as a brain file to retain user information for next time. from then on at initialisation the brain is loaded by

kernel.restoreBrain(self.user+".brn")

If RAM is used effectively there should be quite a high number of users the bot can handle simultaneously, but the most I have done, given a shortage of willing testers is 3 !!

code listing


#!/usr/bin/python
import Skype4Py, aiml, time, os

skype = Skype4Py.Skype()
skype.Attach()

class Bot():
    def __init__(self,user):
        self.user = user
        self.kernel = aiml.Kernel()
        if os.path.isfile(self.user+".brn"):
            self.kernel.restoreBrain(self.user+".brn")
        else:
            self.kernel.bootstrap(learnFiles="std-startup.xml", commands="load aiml b")
        self.last = ''
        self.response = ''
        self.commandz={'bye':self.bye}

    def bye(self):
        self.kernel.saveBrain(self.user+".brn")

    def fetch(self):
        z = skype.Messages(self.user)[0]
        if z.Status == 'RECEIVED':
            self.last = z.Body
        else:
            self.last =''
        z.MarkAsSeen()

    def reply(self):
        if self.last:
            if self.last in self.commandz:
                self.commandz[self.last]()
            else:
                self.response = self.kernel.respond(self.last)
                skype.SendMessage(self.user,self.response)

users = ['luke','friend#01'] # more users here as you find them

bots = []
for user in users:
    bots.append(Bot(user))

while True:
    for b in bots:
        b.fetch()
        b.reply()
        time.sleep(3.0)

now you can leave the bot running on your development machine and talk to your new friend from anywhere from any device that can connect to Skype.

Discussion

This code should be enough to get you up and running with a simple start point. There are huge possibilities for developing more features using the simple framework I have given. For one thing you could develop your own aiml brain that diverges from ALICE. A customer service system, a honeytrap that mimics a trusting person to catch phishers, a bot that talks naughty… the sky is the limit. If you want it to switch on your lights or run your smart home then AIML allows shell commands that can do nearly anything a shell can do, so controlling peripheral stuff should be possible. I’ll leave that to bold explorers.

As well as customising the aiml, more commands can also be added to the “commandz” list to shift some of the function into Python instead of the aiml. for instance get Python to make a shell command using curl to get weather, stock data etc from online providers.

In terms of security I don’t think a cracker could get control of your system like this because they cannot get a shell from the bot (disagree anyone?). If your aiml contains tags that are designed to perform shell calls then hacking becomes a possibility and security measures would be necessary, such as making very sure su or sudo cannot be passed to the shell. Also making the bot code run as its own user with very limited permissions would make things safer. Or if you are lazy just vet the contacts that the bot has in its list…

quick and dirty, just the way I like my coffee.

Advertisements

Written by Luke Dunn

April 15, 2017 at 10:44 am

Posted in Python, script

Tagged with , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: