8 Commits
0.0.1 ... 0.0.2

Author SHA1 Message Date
0c260c9a08 Version 0.0.2 2022-06-08 20:02:16 +01:00
a2692fecc9 Fix linting issues 2022-06-08 19:56:16 +01:00
8f5218c2bb Fix owner env var 2022-06-08 19:27:05 +01:00
237db7fa7d Add health endpoint 2022-06-08 19:04:17 +01:00
f87f2f68a4 Fix syntax issues 2022-06-08 19:00:32 +01:00
c6a6ffbc9f Update README 2022-06-08 19:00:03 +01:00
509c6f7c43 Notify the owner of subscriptions 2022-06-08 18:59:57 +01:00
d54c860b16 Add owner ID and default subscriber settings 2022-06-08 18:57:02 +01:00
4 changed files with 54 additions and 25 deletions

View File

@@ -9,3 +9,16 @@ Forked from [FiveBoroughs/Twilio2Telegram](https://github.com/FiveBoroughs/Twili
This simple tool acts as a webhook receiver for the Twilio API, taking messages sent over and posting them on Telegram to a target chat or channel. This is useful for forwarding on 2FA messages, notification text messages for services that don't support international numbers (*cough* Disney, of all people). This simple tool acts as a webhook receiver for the Twilio API, taking messages sent over and posting them on Telegram to a target chat or channel. This is useful for forwarding on 2FA messages, notification text messages for services that don't support international numbers (*cough* Disney, of all people).
The bot is designed to run within a Kubernetes environment, but can be operated as a individual container, or as a hand ran service. The bot is designed to run within a Kubernetes environment, but can be operated as a individual container, or as a hand ran service.
## Configuration
All configuration is provided via environment variables
| Variable | Required? | Description |
| -------------------------- | --------- | --------------------------------------------------------------------------- |
| SMSBOT_DEFAULT_SUBSCRIBERS | No | A list of IDs, seperated by commas, to add to the subscribers list on start |
| SMSBOT_LISTEN_HOST | No | The host for the webhooks to listen on, defaults to `0.0.0.0` |
| SMSBOT_LISTEN_PORT | No | The port to listen to, defaults to `80` |
| SMSBOT_OWNER_ID | No | ID of the owner of this bot |
| SMSBOT_TELEGRAM_BOT_TOKEN | Yes | Your Bot Token for Telegram |
| SMSBOT_TWILIO_AUTH_TOKEN | No | Twilio auth token, used to validate any incoming webhook calls |

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = smsbot name = smsbot
version = 0.0.1 version = 0.0.2
description = A simple Telegram bot to receive SMS messages. description = A simple Telegram bot to receive SMS messages.
long_description = file: README.md, LICENSE long_description = file: README.md, LICENSE
license = MIT license = MIT
@@ -27,9 +27,9 @@ console_scripts =
[flake8] [flake8]
format = wemake format = wemake
ignore = E501,D,WPS226,WPS110, WPS210,WPS231,WPS202 ignore = D,E501,WPS432,WPS323,WPS2,S104,WPS412
max-line-length = 120 max-line-length = 120
exclude = setup.py exclude = setup.py,env,build
[darglint] [darglint]
docstring_style=sphinx docstring_style=sphinx

View File

@@ -3,23 +3,22 @@ import os
import sys import sys
from functools import wraps from functools import wraps
import pkg_resources
from flask import Flask, abort, current_app, request from flask import Flask, abort, current_app, request
from telegram.ext import CommandHandler, Updater from telegram.ext import CommandHandler, Updater
from twilio.request_validator import RequestValidator from twilio.request_validator import RequestValidator
from waitress import serve from waitress import serve
import pkg_resources
pkg_version = pkg_resources.require("smsbot")[0].version pkg_version = pkg_resources.require('smsbot')[0].version
class TelegramSmsBot(object): class TelegramSmsBot(object):
owner_id = None def __init__(self, token, owner=None, subscribers=None):
subscriber_ids = []
def __init__(self, token):
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.bot_token = token self.bot_token = token
self.owner_id = owner
self.subscriber_ids = subscribers or []
def start(self): def start(self):
self.logger.info('Starting bot...') self.logger.info('Starting bot...')
@@ -44,9 +43,10 @@ class TelegramSmsBot(object):
def subscribe_handler(self, update, context): def subscribe_handler(self, update, context):
self.logger.info('/subscribe command received') self.logger.info('/subscribe command received')
if not update.message.chat['id'] in self.subscriber_ids: if update.message.chat['id'] not in self.subscriber_ids:
self.logger.info('{0} subscribed'.format(update.message.chat['username'])) self.logger.info('{0} subscribed'.format(update.message.chat['username']))
self.subscriber_ids.append(update.message.chat['id']) self.subscriber_ids.append(update.message.chat['id'])
self.send_owner('{0} has subscribed'.format(update.message.chat['username']))
update.message.reply_markdown('You have been subscribed to SMS notifications') update.message.reply_markdown('You have been subscribed to SMS notifications')
else: else:
update.message.reply_markdown('You are already subscribed to SMS notifications') update.message.reply_markdown('You are already subscribed to SMS notifications')
@@ -56,6 +56,7 @@ class TelegramSmsBot(object):
if update.message.chat['id'] in self.subscriber_ids: if update.message.chat['id'] in self.subscriber_ids:
self.logger.info('{0} unsubscribed'.format(update.message.chat['username'])) self.logger.info('{0} unsubscribed'.format(update.message.chat['username']))
self.subscriber_ids.remove(update.message.chat['id']) self.subscriber_ids.remove(update.message.chat['id'])
self.send_owner('{0} has unsubscribed'.format(update.message.chat['username']))
update.message.reply_markdown('You have been unsubscribed to SMS notifications') update.message.reply_markdown('You have been unsubscribed to SMS notifications')
else: else:
update.message.reply_markdown('You are not subscribed to SMS notifications') update.message.reply_markdown('You are not subscribed to SMS notifications')
@@ -63,6 +64,7 @@ class TelegramSmsBot(object):
def error_handler(self, update, context): def error_handler(self, update, context):
"""Log Errors caused by Updates.""" """Log Errors caused by Updates."""
self.logger.warning('Update "%s" caused error "%s"', update, context.error) self.logger.warning('Update "%s" caused error "%s"', update, context.error)
self.send_owner('Update "%{0}" caused error "{1}"'.format(update, context.error))
def send_message(self, message, chat_id): def send_message(self, message, chat_id):
self.bot.sendMessage(text=message, chat_id=chat_id) self.bot.sendMessage(text=message, chat_id=chat_id)
@@ -82,16 +84,16 @@ class TelegramSmsBot(object):
self.subscriber_ids.append(chat_id) self.subscriber_ids.append(chat_id)
def validate_twilio_request(f): def validate_twilio_request(func):
"""Validates that incoming requests genuinely originated from Twilio""" """Validates that incoming requests genuinely originated from Twilio"""
@wraps(f) @wraps(func)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs): # noqa: WPS430
# Create an instance of the RequestValidator class # Create an instance of the RequestValidator class
twilio_token = os.environ.get('SMSBOT_TWILIO_AUTH_TOKEN') twilio_token = os.environ.get('SMSBOT_TWILIO_AUTH_TOKEN')
if not twilio_token: if not twilio_token:
current_app.logger.warning('Twilio request validation skipped due to SMSBOT_TWILIO_AUTH_TOKEN missing') current_app.logger.warning('Twilio request validation skipped due to SMSBOT_TWILIO_AUTH_TOKEN missing')
return f(*args, **kwargs) return func(*args, **kwargs)
validator = RequestValidator(twilio_token) validator = RequestValidator(twilio_token)
@@ -100,13 +102,13 @@ def validate_twilio_request(f):
request_valid = validator.validate( request_valid = validator.validate(
request.url, request.url,
request.form, request.form,
request.headers.get('X-TWILIO-SIGNATURE', '')) request.headers.get('X-TWILIO-SIGNATURE', ''),
)
# Continue processing the request if it's valid, return a 403 error if # Continue processing the request if it's valid, return a 403 error if
# it's not # it's not
if request_valid or current_app.debug: if request_valid or current_app.debug:
return f(*args, **kwargs) return func(*args, **kwargs)
else:
return abort(403) return abort(403)
return decorated_function return decorated_function
@@ -116,19 +118,23 @@ class TwilioWebhookHandler(object):
def __init__(self): def __init__(self):
self.app = Flask(self.__class__.__name__) self.app = Flask(self.__class__.__name__)
self.app.add_url_rule('/', 'index', self.index, methods=['GET']) self.app.add_url_rule('/', 'index', self.index, methods=['GET'])
self.app.add_url_rule('/health', 'health', self.health, methods=['GET'])
self.app.add_url_rule('/message', 'message', self.message, methods=['POST']) self.app.add_url_rule('/message', 'message', self.message, methods=['POST'])
self.app.add_url_rule('/call', 'call', self.call, methods=['POST']) self.app.add_url_rule('/call', 'call', self.call, methods=['POST'])
def set_bot(self, bot): def set_bot(self, bot): # noqa: WPS615
self.bot = bot self.bot = bot
def index(self): def index(self):
return 'Smsbot v{0} - {1}'.format(pkg_version, request.values.to_dict()) return ''
def health(self):
return '<h1>Smsbot v{0}</h1><p><b>Owner</b>: {1}</p><p><b>Subscribers</b>: {2}</p>'.format(pkg_version, self.bot.owner_id, self.bot.subscriber_ids)
@validate_twilio_request @validate_twilio_request
def message(self): def message(self):
message = "From: {From}\n\n{Body}".format(**request.values.to_dict()) message = 'From: {From}\n\n{Body}'.format(**request.values.to_dict())
current_app.logger.info('Received SMS from {From}: {Body}'.format(**request.values.to_dict())) current_app.logger.info('Received SMS from {From}: {Body}'.format(**request.values.to_dict()))
self.bot.send_subscribers(message) self.bot.send_subscribers(message)
@@ -160,6 +166,16 @@ def main():
# Start bot # Start bot
telegram_bot = TelegramSmsBot(token) telegram_bot = TelegramSmsBot(token)
# Set the owner ID if configured
if 'SMSBOT_OWNER_ID' in os.environ:
telegram_bot.set_owner(os.environ.get('SMSBOT_OWNER_ID'))
# Add default subscribers
if 'SMSBOT_DEFAULT_SUBSCRIBERS' in os.environ:
for chat_id in os.environ.get('SMSBOT_DEFAULT_SUBSCRIBERS').split(','):
telegram_bot.add_subscriber(chat_id)
telegram_bot.start() telegram_bot.start()
# Start webhooks # Start webhooks

View File

@@ -1,4 +1,4 @@
from . import main from smsbot import main
if __name__ == '__main__': if __name__ == '__main__':
main() main()