Skip to content

Commit d978f5b

Browse files
authored
xmpp wikibot for GCE (#618)
* adding new xmpp wikibot example * xmpp wikibot for GCE
1 parent d14074a commit d978f5b

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

compute/xmpp_wikibot/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Uncyclobot example that can be run on Google Compute Engine
2+
3+
This sample shows how to use the [SleekXMPP](http://sleekxmpp.com/index.html)
4+
client and [Flask](http://flask.pocoo.org/) to build a simple chatbot that can
5+
be run on [Google Compute Engine](https://cloud.google.com/compute/). The
6+
chatbot does two things:
7+
8+
1. Sends messages to XMPP users via http get:
9+
* The server is running on port 5000
10+
* if running on virtual machine use:
11+
`http://<MACHINE IP>:5000/send_message?recipient=<RECIPIENT ADDRESS>&message=<MSG>`
12+
* If running locally use:
13+
`http://localhost:5000/send_message?recipient=<RECIPIENT ADDRESS>&message=<MSG>`
14+
15+
2. Responds to incoming messages with a Uncyclopedia page on the topic:
16+
* Send a message with a topic (e.g., 'Hawaii') to the XMPP account the
17+
server is using
18+
* It should respond with a Uncyclopedia page (when one exists)
19+
20+
## Setup
21+
22+
Follow the instructions at the
23+
[Compute Engine Quickstart Guide](https://cloud.google.com/compute/docs/quickstart-linux)
24+
on how to create a project, create a virtual machine, and connect to your
25+
instance via SSH. Once you have done this, you may jump to
26+
[Installing files and dependencies](#installing-files-and-dependencies).
27+
28+
You should also download the [Google Cloud SDK](https://cloud.google.com/sdk/).
29+
It will allow you to access many of the features of Google Compute Engine via
30+
your local machine.
31+
32+
**IMPORTANT** You must enable tcp traffic on port 5000 to send messages to the
33+
XMPP server. This can be done by running the following SDK commands:
34+
35+
gcloud config set project <YOUR PROJECT NAME>
36+
37+
gcloud compute firewall-rules create wikibot-server-rule --allow tcp:5000 --source-ranges=0.0.0.0/0
38+
39+
Or you can create a new firewall rule via the UI in the
40+
[Networks](https://console.cloud.google.com/networking/networks/list) section of
41+
the Google Cloud Console.
42+
43+
### Installing files and dependencies
44+
45+
First, install the `wikibot.py` and `requirements.txt` files onto your remote
46+
instance. See the guide on
47+
[Transferring Files](https://cloud.google.com/compute/docs/instances/transfer-files)
48+
for more information on how to do this using the Mac file browser, `scp`, or
49+
the Google Cloud SDK.
50+
51+
Before running or deploying this application, you must install the dependencies
52+
using [pip](http://pip.readthedocs.io/en/stable/):
53+
54+
pip install -r requirements.txt
55+
56+
57+
## Running the sample
58+
59+
You'll need to have an XMPP account prior to actually running the sample.
60+
If you do not have one, you can easily create an account at one of the many
61+
XMPP servers such as [Jabber.at](https://jabber.at/account/register/).
62+
Once you have an account, run the following command:
63+
64+
python wikibot.py '<YOUR XMPP USERNAME>' '<PASSWORD>'
65+
66+
Where the username (e.g., '[email protected]') and password for the account that
67+
you'd like to use for your chatbot are passed in as arguments.
68+
69+
Enter control-C to stop the server
70+
71+
72+
### Running on your local machine
73+
74+
You may also run the sample locally by simply copying `wikibot.py` to a project
75+
directory and installing all python dependencies there.

compute/xmpp_wikibot/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Flask==0.11.1
2+
requests==2.11.1
3+
sleekxmpp==1.3.1

compute/xmpp_wikibot/wikibot.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Uncyclobot server example using SleekXMPP client library"""
16+
17+
import argparse
18+
import getpass
19+
import json
20+
import logging
21+
import threading
22+
import urllib
23+
24+
from flask import Flask, request
25+
import requests
26+
import sleekxmpp
27+
28+
29+
app = Flask(__name__)
30+
31+
@app.route('/send_message', methods=['GET'])
32+
def send_message():
33+
recipient = request.args.get('recipient')
34+
message = request.args.get('message')
35+
36+
if chat_client and recipient and message:
37+
chat_client.send_message(mto=recipient, mbody=message)
38+
return 'message sent to {} with body: {}'.format(recipient, message)
39+
else:
40+
logging.info('chat client or recipient or message does not exist!')
41+
return 'message failed to send', 400
42+
43+
44+
def run_server(host='0.0.0.0'):
45+
app.run(threaded=False, use_reloader=False, host=host)
46+
47+
48+
class UncycloBot(sleekxmpp.ClientXMPP):
49+
"""A simple SleekXMPP bot that will take messages, look up their content on
50+
wikipedia and provide a link to the page if it exists.
51+
"""
52+
53+
def __init__(self, jid, password):
54+
sleekxmpp.ClientXMPP.__init__(self, jid, password)
55+
56+
# The session_start event will be triggered when
57+
# the bot establishes its connection with the server
58+
# and the XML streams are ready for use. We want to
59+
# listen for this event so that we we can initialize
60+
# our roster.
61+
self.add_event_handler('session_start', self.start)
62+
63+
# The message event is triggered whenever a message
64+
# stanza is received. Be aware that that includes
65+
# MUC messages and error messages.
66+
self.add_event_handler('message', self.message)
67+
68+
def start(self, event):
69+
"""Process the session_start event.
70+
71+
Typical actions for the session_start event are requesting the roster
72+
and broadcasting an initial presence stanza.
73+
74+
Arguments:
75+
event -- An empty dictionary. The session_start event does not
76+
provide any additional data.
77+
"""
78+
self.send_presence()
79+
self.get_roster()
80+
81+
def message(self, msg):
82+
"""Process incoming message stanzas.
83+
84+
Be aware that this also includes MUC messages and error messages. It is
85+
usually a good idea to check the messages's type before processing or
86+
sending replies. If the message is the appropriate type, then the bot
87+
checks wikipedia to see if the message string exists as a page on the
88+
site. If so, it sends this link back to the sender in the reply.
89+
90+
Arguments:
91+
msg -- The received message stanza. See the SleekXMPP documentation
92+
for stanza objects and the Message stanza to see how it may be
93+
used.
94+
"""
95+
if msg['type'] in ('chat', 'normal'):
96+
msg_body = msg['body']
97+
logging.info('Message sent was: {}'.format(msg_body))
98+
encoded_body = urllib.quote_plus(msg_body)
99+
svrResponse = requests.get(
100+
'https://en.wikipedia.org/w/api.php?'
101+
'action=parse&prop=sections&format=json&page={}'.format(
102+
encoded_body))
103+
doc = json.loads(svrResponse.content)
104+
try:
105+
page_id = str(doc['parse']['pageid'])
106+
defn_url = 'https://en.wikipedia.org/?curid={}'.format(page_id)
107+
msg.reply('find out more about: "{}" here: {}'.format(
108+
msg_body, defn_url)).send()
109+
except KeyError as e:
110+
logging.info('key error: {0}'.format(e))
111+
msg.reply('I wasn\'t able to locate info on "{}" Sorry'.format(
112+
msg_body)).send()
113+
114+
115+
if __name__ == '__main__':
116+
# Setup the command line arguments.
117+
parser = argparse.ArgumentParser(
118+
description=__doc__,
119+
formatter_class=argparse.RawDescriptionHelpFormatter)
120+
121+
# Output verbosity options.
122+
parser.add_argument(
123+
'-q', '--quiet', help='set logging to ERROR', action='store_const',
124+
dest='loglevel', const=logging.ERROR, default=logging.INFO)
125+
parser.add_argument(
126+
'-d', '--debug', help='set logging to DEBUG', action='store_const',
127+
dest='loglevel', const=logging.DEBUG, default=logging.INFO)
128+
parser.add_argument(
129+
'-v', '--verbose', help='set logging to COMM', action='store_const',
130+
dest='loglevel', const=5, default=logging.INFO)
131+
132+
# JID and password options.
133+
parser.add_argument('-j', '--jid', help='JID to use', required=True)
134+
parser.add_argument('-p', '--password', help='password to use')
135+
136+
args = parser.parse_args()
137+
138+
# Setup logging.
139+
logging.basicConfig(level=args.loglevel,
140+
format='%(levelname)-8s %(message)s')
141+
142+
if args.password is None:
143+
args.password = getpass.getpass('Password: ')
144+
145+
# Setup the UncycloBot and register plugins. Note that while plugins may
146+
# have interdependencies, the order in which you register them does
147+
# not matter.
148+
xmpp = UncycloBot(args.jid, args.password)
149+
xmpp.register_plugin('xep_0030') # Service Discovery
150+
xmpp.register_plugin('xep_0004') # Data Forms
151+
xmpp.register_plugin('xep_0060') # PubSub
152+
xmpp.register_plugin('xep_0199') # XMPP Ping
153+
154+
chat_client = xmpp # set the global variable
155+
156+
# start the app server and run it as a thread so that the XMPP server may
157+
# also start
158+
threading.Thread(target=run_server).start()
159+
160+
# Connect to the XMPP server and start processing XMPP stanzas.
161+
if xmpp.connect():
162+
xmpp.process(block=True)
163+
print('Done')
164+
else:
165+
print('Unable to connect.')

0 commit comments

Comments
 (0)