Add SMS module
This commit is contained in:
parent
4daf05858b
commit
9ed3d3d465
65
docs/api/modules/sms.rst
Normal file
65
docs/api/modules/sms.rst
Normal 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:
|
@ -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' && \
|
||||
|
11
examples/data/config/SMS.json
Normal file
11
examples/data/config/SMS.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"account_sid": "xxxx",
|
||||
"auth_token": "xxxx",
|
||||
"number": "+10000000000",
|
||||
"api_port": 3000,
|
||||
"channel": "#foo",
|
||||
"contacts": {
|
||||
"guy1": "+11111111111",
|
||||
"guy2": "+12222222222"
|
||||
}
|
||||
}
|
@ -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
150
pyircbot/modules/SMS.py
Normal 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))
|
Loading…
Reference in New Issue
Block a user