Skip to content

xmpp wikibot for GCE #618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions compute/xmpp_wikibot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Uncyclobot example that can be run on Google Compute Engine

This sample shows how to use the [SleekXMPP](http://sleekxmpp.com/index.html)
client and [Flask](http://flask.pocoo.org/) to build a simple chatbot that can
be run on [Google Compute Engine](https://cloud.google.com/compute/). The
chatbot does two things:

1. Sends messages to XMPP users via http get:
* The server is running on port 5000
* if running on virtual machine use:
`http://<MACHINE IP>:5000/send_message?recipient=<RECIPIENT ADDRESS>&message=<MSG>`
* If running locally use:
`http://localhost:5000/send_message?recipient=<RECIPIENT ADDRESS>&message=<MSG>`

2. Responds to incoming messages with a Uncyclopedia page on the topic:
* Send a message with a topic (e.g., 'Hawaii') to the XMPP account the
server is using
* It should respond with a Uncyclopedia page (when one exists)

## Setup

Follow the instructions at the
[Compute Engine Quickstart Guide](https://cloud.google.com/compute/docs/quickstart-linux)
on how to create a project, create a virtual machine, and connect to your
instance via SSH. Once you have done this, you may jump to
[Installing files and dependencies](#installing-files-and-dependencies).

You should also download the [Google Cloud SDK](https://cloud.google.com/sdk/).
It will allow you to access many of the features of Google Compute Engine via
your local machine.

**IMPORTANT** You must enable tcp traffic on port 5000 to send messages to the
XMPP server. This can be done by running the following SDK commands:

gcloud config set project <YOUR PROJECT NAME>

gcloud compute firewall-rules create wikibot-server-rule --allow tcp:5000 --source-ranges=0.0.0.0/0

Or you can create a new firewall rule via the UI in the
[Networks](https://console.cloud.google.com/networking/networks/list) section of
the Google Cloud Console.

### Installing files and dependencies

First, install the `wikibot.py` and `requirements.txt` files onto your remote
instance. See the guide on
[Transferring Files](https://cloud.google.com/compute/docs/instances/transfer-files)
for more information on how to do this using the Mac file browser, `scp`, or
the Google Cloud SDK.

Before running or deploying this application, you must install the dependencies
using [pip](http://pip.readthedocs.io/en/stable/):

pip install -r requirements.txt


## Running the sample

You'll need to have an XMPP account prior to actually running the sample.
If you do not have one, you can easily create an account at one of the many
XMPP servers such as [Jabber.at](https://jabber.at/account/register/).
Once you have an account, run the following command:

python wikibot.py '<YOUR XMPP USERNAME>' '<PASSWORD>'

Where the username (e.g., '[email protected]') and password for the account that
you'd like to use for your chatbot are passed in as arguments.

Enter control-C to stop the server


### Running on your local machine

You may also run the sample locally by simply copying `wikibot.py` to a project
directory and installing all python dependencies there.
3 changes: 3 additions & 0 deletions compute/xmpp_wikibot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Flask==0.11.1
requests==2.11.1
sleekxmpp==1.3.1
165 changes: 165 additions & 0 deletions compute/xmpp_wikibot/wikibot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Uncyclobot server example using SleekXMPP client library"""

import argparse
import getpass
import json
import logging
import threading
import urllib

from flask import Flask, request
import requests
import sleekxmpp


app = Flask(__name__)

@app.route('/send_message', methods=['GET'])
def send_message():
recipient = request.args.get('recipient')
message = request.args.get('message')

if chat_client and recipient and message:
chat_client.send_message(mto=recipient, mbody=message)
return 'message sent to {} with body: {}'.format(recipient, message)
else:
logging.info('chat client or recipient or message does not exist!')
return 'message failed to send', 400


def run_server(host='0.0.0.0'):
app.run(threaded=False, use_reloader=False, host=host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug=False here, please. We shouldn't be using Flask's dev server to serve public traffic at all, but at least disable the debug flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I know . The setup gets increasingly more complex if I try to do the right thing with a real web server. Jerjou and I discussed possibly removing the server component altogether as the chat interactions are more interesting anyway. Will refine tomorrow. Thanks for all the help!



class UncycloBot(sleekxmpp.ClientXMPP):
"""A simple SleekXMPP bot that will take messages, look up their content on
wikipedia and provide a link to the page if it exists.
"""

def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)

# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler('session_start', self.start)

# The message event is triggered whenever a message
# stanza is received. Be aware that that includes
# MUC messages and error messages.
self.add_event_handler('message', self.message)

def start(self, event):
"""Process the session_start event.

Typical actions for the session_start event are requesting the roster
and broadcasting an initial presence stanza.

Arguments:
event -- An empty dictionary. The session_start event does not
provide any additional data.
"""
self.send_presence()
self.get_roster()

def message(self, msg):
"""Process incoming message stanzas.

Be aware that this also includes MUC messages and error messages. It is
usually a good idea to check the messages's type before processing or
sending replies. If the message is the appropriate type, then the bot
checks wikipedia to see if the message string exists as a page on the
site. If so, it sends this link back to the sender in the reply.

Arguments:
msg -- The received message stanza. See the SleekXMPP documentation
for stanza objects and the Message stanza to see how it may be
used.
"""
if msg['type'] in ('chat', 'normal'):
msg_body = msg['body']
logging.info('Message sent was: {}'.format(msg_body))
encoded_body = urllib.quote_plus(msg_body)
svrResponse = requests.get(
'https://en.wikipedia.org/w/api.php?'
'action=parse&prop=sections&format=json&page={}'.format(
encoded_body))
doc = json.loads(svrResponse.content)
try:
page_id = str(doc['parse']['pageid'])
defn_url = 'https://en.wikipedia.org/?curid={}'.format(page_id)
msg.reply('find out more about: "{}" here: {}'.format(
msg_body, defn_url)).send()
except KeyError as e:
logging.info('key error: {0}'.format(e))
msg.reply('I wasn\'t able to locate info on "{}" Sorry'.format(
msg_body)).send()


if __name__ == '__main__':
# Setup the command line arguments.
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)

# Output verbosity options.
parser.add_argument(
'-q', '--quiet', help='set logging to ERROR', action='store_const',
dest='loglevel', const=logging.ERROR, default=logging.INFO)
parser.add_argument(
'-d', '--debug', help='set logging to DEBUG', action='store_const',
dest='loglevel', const=logging.DEBUG, default=logging.INFO)
parser.add_argument(
'-v', '--verbose', help='set logging to COMM', action='store_const',
dest='loglevel', const=5, default=logging.INFO)

# JID and password options.
parser.add_argument('-j', '--jid', help='JID to use', required=True)
parser.add_argument('-p', '--password', help='password to use')

args = parser.parse_args()

# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')

if args.password is None:
args.password = getpass.getpass('Password: ')

# Setup the UncycloBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = UncycloBot(args.jid, args.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping

chat_client = xmpp # set the global variable

# start the app server and run it as a thread so that the XMPP server may
# also start
threading.Thread(target=run_server).start()

# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
xmpp.process(block=True)
print('Done')
else:
print('Unable to connect.')