Add SMS module

This commit is contained in:
dave 2017-11-20 18:54:50 -08:00
parent 4daf05858b
commit 9ed3d3d465
5 changed files with 228 additions and 2 deletions

65
docs/api/modules/sms.rst Normal file
View File

@ -0,0 +1,65 @@
:mod:`SMS` --- A simple SMS client
==================================
This module provides ".text-<name> <message>" commands that send SMS messages
via the Twilio api.
Config
------
.. code-block:: json
{
"account_sid": "xxxx",
"auth_token": "xxxx",
"number": "+10000000000",
"api_port": 3000,
"channel": "#foo",
"contacts": {
"guy1": "+11111111111",
"guy2": "+12222222222"
},
}
.. cmdoption:: account_sid
Twilio account ID
.. cmdoption:: auth_token
Twilio auth token
.. cmdoption:: number
Twilio phone number. Must match the format shown above.
.. cmdoption:: api_port
HTTP port to listen for Twilio webhook requests on. Using `-1` disables webhook listening.
.. cmdoption:: channel
Channel the module is enabled on
.. cmdoption:: contacts
Dict of names to phone numbers. Names must be a-zA-Z0-9 and numbers match the format shown above.
Twilio Setup
------------
In Twilio's UI, create a "Messaging Service" of the "Notifications, 2-Way"
type. Check "Process inbound messages" if desired and enter the bot's webhook
URL.
The webhook listener listens on `/app/gotsms`, so an example webhook URL would
be `http://1.2.3.4:3000/app/gotsms`.
Class Reference
---------------
.. automodule:: pyircbot.modules.SMS
:members:
:undoc-members:
:show-inheritance:

View File

@ -3,7 +3,7 @@ FROM ubuntu:artful
RUN apt-get update ; \
apt-get install -y python3 python3-sphinx python3-setuptools python3-dev python3-requests python3-pip python3-lxml make wget unzip libmysqlclient-dev
RUN pip3 install pytz praw releases
RUN pip3 install pytz praw releases cherrypy twilio==6.9.0
RUN cd /tmp ; \
wget -O msgbus.tar.gz 'http://gitlab.davepedu.com/dave/pymsgbus/repository/archive.tar.gz?ref=master' && \

View File

@ -0,0 +1,11 @@
{
"account_sid": "xxxx",
"auth_token": "xxxx",
"number": "+10000000000",
"api_port": 3000,
"channel": "#foo",
"contacts": {
"guy1": "+11111111111",
"guy2": "+12222222222"
}
}

View File

@ -7,7 +7,7 @@ CMD ["-c", "config.json"]
RUN apt-get update && \
apt-get install -y python3 python3-setuptools python3-requests curl unzip sqlite3 && \
easy_install3 pip && \
pip3 install praw==5.0.1 pytz && \
pip3 install praw==5.0.1 pytz cherrypy twilio==6.9.0 && \
cd /tmp && \
curl -o msgbus.tar.gz 'http://gitlab.davepedu.com/dave/pymsgbus/repository/archive.tar.gz?ref=master' && \
mkdir pymsgbus && tar zxvf msgbus.tar.gz --strip-components 1 -C pymsgbus/ &&\

150
pyircbot/modules/SMS.py Normal file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""
.. module::SMS
:synopsis: SMS client script (requires Twilio account)
"""
from pyircbot.modulebase import ModuleBase, regex
import cherrypy
from threading import Thread
from twilio.rest import Client
class Api(object):
def __init__(self, mod):
self.mod = mod
@cherrypy.expose
def gotsms(self, *args, **kwargs):
"""
Twilio webhook listener
"""
"""
Example payload:
{'To': '+11234567890',
'ToCity': 'ALBERTVILLE',
'ToState': 'AL',
'ToZip': '35951',
'ToCountry': 'US,
'NumMedia': '1',
'MediaContentType0': 'image/jpeg',
'MediaUrl0': 'https://api.twilio.com/xxx',
'From': '+11234567890',
'FromCity': 'ROCHESTER',
'FromState': 'NY',
'FromZip': '14622',
'FromCountry': 'US',
'Body': 'Lol',
'NumSegments': '1',
'SmsStatus': 'received',
'SmsSid': 'xxx',
'SmsMessageSid': 'xxx',
'MessageSid': 'xxx',
'AccountSid': 'xxx',
'MessagingServiceSid': 'xxx',
'ApiVersion': '2010-04-01'}
"""
attachments = []
medias = int(kwargs["NumMedia"])
while medias > 0:
medias -= 1
attachments.append((kwargs["MediaContentType{}".format(medias)], kwargs["MediaUrl{}".format(medias)], ))
self.mod.got_text(kwargs["From"], kwargs["Body"], attachments=attachments)
yield ''
class SMS(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
self.apithread = None
self.twilio = Client(self.config["account_sid"], self.config["auth_token"])
def api(self):
"""
Run the webhook listener and block
"""
api = Api(self)
cherrypy.config.update({
# 'sessionFilter.on': True,
'tools.sessions.on': False,
'tools.sessions.locking': 'explicit',
# 'tools.sessions.timeout': 525600,
'request.show_tracebacks': True,
'server.socket_port': self.config.get("api_port"),
'server.thread_pool': 1,
'server.socket_host': '0.0.0.0',
'server.show_tracebacks': True,
'server.socket_timeout': 10,
'log.screen': False,
'engine.autoreload.on': False})
cherrypy.tree.mount(api, '/app/', {})
cherrypy.engine.start()
cherrypy.engine.block()
def onenable(self):
"""
If needed, create an API and run it
"""
if self.apithread is None and self.config.get("api_port") > 0:
self.apithread = Thread(target=self.api, daemon=True)
self.apithread.start()
def ondisable(self):
"""
Shut down the api
"""
cherrypy.engine.exit()
@regex(r'(?:^\.text\-([a-zA-Z0-9]+)(?:\s+(.+))?)', types=['PRIVMSG'])
def cmd_text(self, match, msg):
"""
Text somebody
"""
contact, message = match.groups()
contact = contact.lower()
if msg.args[0].lower() != self.config["channel"].lower():
return # invalid channel
if message is None:
return # TODO help text
if contact not in self.config["contacts"].keys():
return # TODO invalid contact
try:
self.twilio.api.account.messages.create(to=self.config["contacts"][contact],
from_=self.config["number"],
body=msg.trailing[7 + len(contact):].strip())
except Exception as e:
self.bot.act_PRIVMSG(msg.args[0], "Could not send message: {}".format(repr(e)))
else:
self.bot.act_PRIVMSG(msg.args[0], "Message sent.")
def got_text(self, sender, body, attachments=None):
"""
Webhook callback to react to a message
:param sender: number that sent the message, like +10000000000
:type sender: str
:param body: body text of the sms/mms
:type body: str
:param attachments: if mms, any attachments as a list of (mime, url) tuples
:type attachments: list
"""
name = None
for contact, number in self.config["contacts"].items():
if number == sender:
name = contact
if name is None:
name = sender
body = body.strip()
if body:
self.bot.act_PRIVMSG(self.config["channel"], "SMS from {}: {}".format(name, body))
if attachments:
for mime, url in attachments[0:3]:
self.bot.act_PRIVMSG(self.config["channel"], "MMS from {}: {} ({})".format(name, url, mime))