mirror of
https://github.com/nikdoof/smsbot.git
synced 2025-12-13 18:12:15 +00:00
Compare commits
14 Commits
smsbot-hel
...
smsbot-hel
| Author | SHA1 | Date | |
|---|---|---|---|
| d918d95c7f | |||
| 70c71ff731 | |||
| b9dcd8a880 | |||
| 6e569e16c7 | |||
|
|
d1b1f94dc7 | ||
|
|
e762f99168 | ||
|
2c35051b54
|
|||
|
dea3ae84bd
|
|||
|
5d4f83bdc3
|
|||
|
7f481c47ed
|
|||
|
db04de242a
|
|||
|
5a61444535
|
|||
|
a89c560145
|
|||
|
9adaf6c663
|
2
.github/workflows/release-chart.yaml
vendored
2
.github/workflows/release-chart.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
version: v3.6.3
|
version: v3.6.3
|
||||||
|
|
||||||
- name: Run chart-releaser
|
- name: Run chart-releaser
|
||||||
uses: helm/chart-releaser-action@v1.2.1
|
uses: helm/chart-releaser-action@v1.4.0
|
||||||
env:
|
env:
|
||||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
CR_RELEASE_NAME_TEMPLATE: "smsbot-helm-chart-{{ .Version }}"
|
CR_RELEASE_NAME_TEMPLATE: "smsbot-helm-chart-{{ .Version }}"
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -13,6 +13,8 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
- run: pip install -r requirements-dev.txt
|
- run: pip install -r requirements-dev.txt
|
||||||
|
|
||||||
- name: Build Assets
|
- name: Build Assets
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -22,3 +22,16 @@ All configuration is provided via environment variables
|
|||||||
| SMSBOT_OWNER_ID | No | ID of the owner of this bot |
|
| SMSBOT_OWNER_ID | No | ID of the owner of this bot |
|
||||||
| SMSBOT_TELEGRAM_BOT_TOKEN | Yes | Your Bot Token for Telegram |
|
| 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 |
|
| SMSBOT_TWILIO_AUTH_TOKEN | No | Twilio auth token, used to validate any incoming webhook calls |
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
To configure SMSBot, you'll need a Twilio account, either paid or trial is fine.
|
||||||
|
|
||||||
|
* Setup a number in the location you want.
|
||||||
|
* Under Phone Numbers -> Manage -> Active Numbers, click the number you want to setup.
|
||||||
|
* In the "Voice & Fax" section, update the "A Call Comes In" to the URL of your SMSBot instance, with the endpoint being `/call`, e.g. `http://mymachine.test.com/call`
|
||||||
|
* In the "Messaging" section, update the "A Message Comes In" to the URL of your SMSBot instance, with the endpoint being `/message`, e.g. `http://mymachine.test.com/message`
|
||||||
|
|
||||||
|
Your bot should now receive messages, on Telegram you need to start a chat or invite it into any channels you want, then update the `SMSBOT_DEFAULT_SUBSCRIBERS` values with their IDs.
|
||||||
|
|
||||||
|
**Note**: You cannot send test messages from your Twilio account to your Twilio numbers, they'll be silently dropped or fail with an "Invalid Number" error.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ apiVersion: v2
|
|||||||
appVersion: 0.0.3
|
appVersion: 0.0.3
|
||||||
description: A simple Telegram bot to receive SMS messages.
|
description: A simple Telegram bot to receive SMS messages.
|
||||||
name: smsbot
|
name: smsbot
|
||||||
version: 0.0.3
|
version: 0.0.4
|
||||||
kubeVersion: ">=1.19.0-0"
|
kubeVersion: ">=1.19.0-0"
|
||||||
keywords:
|
keywords:
|
||||||
- smsbot
|
- smsbot
|
||||||
@@ -15,4 +15,4 @@ maintainers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
- name: common
|
- name: common
|
||||||
repository: https://library-charts.k8s-at-home.com
|
repository: https://library-charts.k8s-at-home.com
|
||||||
version: 4.3.0
|
version: 4.4.2
|
||||||
|
|||||||
1
requirements-dev.txt
Normal file
1
requirements-dev.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
wheel
|
||||||
@@ -2,3 +2,4 @@ flask
|
|||||||
waitress
|
waitress
|
||||||
twilio
|
twilio
|
||||||
python-telegram-bot
|
python-telegram-bot
|
||||||
|
prometheus_client
|
||||||
@@ -20,6 +20,7 @@ install_requires =
|
|||||||
waitress
|
waitress
|
||||||
twilio
|
twilio
|
||||||
python-telegram-bot
|
python-telegram-bot
|
||||||
|
prometheus_client
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
@@ -11,27 +11,31 @@ pkg_version = pkg_resources.require('smsbot')[0].version
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logging.basicConfig(level=logging.INFO)
|
parser = argparse.ArgumentParser('smsbot')
|
||||||
|
parser.add_argument('--listen-host', default=os.environ.get('SMSBOT_LISTEN_HOST') or '0.0.0.0')
|
||||||
|
parser.add_argument('--listen-port', default=os.environ.get('SMSBOT_LISTEN_PORT') or '80')
|
||||||
|
parser.add_argument('--telegram-bot-token', default=os.environ.get('SMSBOT_TELEGRAM_BOT_TOKEN'))
|
||||||
|
parser.add_argument('--owner-id', default=os.environ.get('SMSBOT_OWNER_ID'))
|
||||||
|
parser.add_argument('--default-subscribers', default=os.environ.get('SMSBOT_DEFAULT_SUBSCRIBERS'))
|
||||||
|
parser.add_argument('--log-level', default='INFO')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.getLevelName(args.log_level))
|
||||||
logging.info('smsbot v%s', pkg_version)
|
logging.info('smsbot v%s', pkg_version)
|
||||||
|
logging.debug('Arguments: %s', args)
|
||||||
listen_host = os.environ.get('SMSBOT_LISTEN_HOST') or '0.0.0.0'
|
|
||||||
listen_port = int(os.environ.get('SMSBOT_LISTEN_PORT') or '80')
|
|
||||||
|
|
||||||
token = os.environ.get('SMSBOT_TELEGRAM_BOT_TOKEN')
|
|
||||||
if not token:
|
|
||||||
logging.error('Telegram Bot token missing')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Start bot
|
# Start bot
|
||||||
telegram_bot = TelegramSmsBot(token)
|
telegram_bot = TelegramSmsBot(args.telegram_bot_token)
|
||||||
|
|
||||||
# Set the owner ID if configured
|
# Set the owner ID if configured
|
||||||
if 'SMSBOT_OWNER_ID' in os.environ:
|
if args.owner_id:
|
||||||
telegram_bot.set_owner(os.environ.get('SMSBOT_OWNER_ID'))
|
telegram_bot.set_owner(args.owner_id)
|
||||||
|
else:
|
||||||
|
logging.warning('No Owner ID is set, which is not a good idea...')
|
||||||
|
|
||||||
# Add default subscribers
|
# Add default subscribers
|
||||||
if 'SMSBOT_DEFAULT_SUBSCRIBERS' in os.environ:
|
if args.default_subscribers:
|
||||||
for chat_id in os.environ.get('SMSBOT_DEFAULT_SUBSCRIBERS').split(','):
|
for chat_id in args.default_subscribers.split(','):
|
||||||
telegram_bot.add_subscriber(chat_id)
|
telegram_bot.add_subscriber(chat_id)
|
||||||
|
|
||||||
telegram_bot.start()
|
telegram_bot.start()
|
||||||
@@ -39,4 +43,4 @@ def main():
|
|||||||
# Start webhooks
|
# Start webhooks
|
||||||
webhooks = TwilioWebhookHandler()
|
webhooks = TwilioWebhookHandler()
|
||||||
webhooks.set_bot(telegram_bot)
|
webhooks.set_bot(telegram_bot)
|
||||||
webhooks.serve(host=listen_host, port=listen_port)
|
webhooks.serve(host=args.listen_host, port=args.listen_port)
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from prometheus_client import Counter, Summary
|
||||||
from telegram.ext import CommandHandler, Updater
|
from telegram.ext import CommandHandler, Updater
|
||||||
|
|
||||||
from smsbot.utils import get_smsbot_version
|
from smsbot.utils import get_smsbot_version
|
||||||
|
|
||||||
|
REQUEST_TIME = Summary('telegram_request_processing_seconds', 'Time spent processing request')
|
||||||
|
COMMAND_COUNT = Counter('telegram_command_count', 'Total number of commands processed')
|
||||||
|
|
||||||
|
|
||||||
class TelegramSmsBot(object):
|
class TelegramSmsBot(object):
|
||||||
|
|
||||||
def __init__(self, token, owner=None, subscribers=None):
|
def __init__(self, telegram_token, allow_subscribing=False, owner=None, subscribers=None):
|
||||||
self.logger = logging.getLogger(self.__class__.__name__)
|
self.logger = logging.getLogger(self.__class__.__name__)
|
||||||
self.bot_token = token
|
self.bot_token = telegram_token
|
||||||
self.owner_id = owner
|
|
||||||
self.subscriber_ids = subscribers or []
|
self.subscriber_ids = subscribers or []
|
||||||
|
self.set_owner(owner)
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.logger.info('Starting bot...')
|
|
||||||
self.updater = Updater(self.bot_token, use_context=True)
|
self.updater = Updater(self.bot_token, use_context=True)
|
||||||
self.updater.dispatcher.add_handler(CommandHandler('help', self.help_handler))
|
self.updater.dispatcher.add_handler(CommandHandler('help', self.help_handler))
|
||||||
self.updater.dispatcher.add_handler(CommandHandler('start', self.help_handler))
|
self.updater.dispatcher.add_handler(CommandHandler('start', self.help_handler))
|
||||||
|
|
||||||
|
if allow_subscribing:
|
||||||
self.updater.dispatcher.add_handler(CommandHandler('subscribe', self.subscribe_handler))
|
self.updater.dispatcher.add_handler(CommandHandler('subscribe', self.subscribe_handler))
|
||||||
self.updater.dispatcher.add_handler(CommandHandler('unsubscribe', self.unsubscribe_handler))
|
self.updater.dispatcher.add_handler(CommandHandler('unsubscribe', self.unsubscribe_handler))
|
||||||
|
|
||||||
self.updater.dispatcher.add_error_handler(self.error_handler)
|
self.updater.dispatcher.add_error_handler(self.error_handler)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.logger.info('Starting bot...')
|
||||||
self.updater.start_polling()
|
self.updater.start_polling()
|
||||||
self.bot = self.updater.bot
|
self.bot = self.updater.bot
|
||||||
self.logger.info('Bot Ready')
|
self.logger.info('Bot Ready')
|
||||||
@@ -29,11 +36,19 @@ class TelegramSmsBot(object):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self.updater.stop()
|
self.updater.stop()
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
def help_handler(self, update, context):
|
def help_handler(self, update, context):
|
||||||
"""Send a message when the command /help is issued."""
|
"""Send a message when the command /help is issued."""
|
||||||
self.logger.info('/help command received in chat: %s', update.message.chat)
|
self.logger.info('/help command received in chat: %s', update.message.chat)
|
||||||
update.message.reply_markdown('Smsbot v{0}\n\n/help\n/subscribe\n/unsubscribe'.format(get_smsbot_version()))
|
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
for command in self.updater.dispatcher.handlers[0]:
|
||||||
|
commands.extend(['/{0}'.format(cmd) for cmd in command.command])
|
||||||
|
|
||||||
|
update.message.reply_markdown('Smsbot v{0}\n\n{1}'.format(get_smsbot_version(), '\n'.join(commands)))
|
||||||
|
COMMAND_COUNT.inc()
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
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 update.message.chat['id'] not in self.subscriber_ids:
|
if update.message.chat['id'] not in self.subscriber_ids:
|
||||||
@@ -43,7 +58,9 @@ class TelegramSmsBot(object):
|
|||||||
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')
|
||||||
|
COMMAND_COUNT.inc()
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
def unsubscribe_handler(self, update, context):
|
def unsubscribe_handler(self, update, context):
|
||||||
self.logger.info('/unsubscribe command received')
|
self.logger.info('/unsubscribe command received')
|
||||||
if update.message.chat['id'] in self.subscriber_ids:
|
if update.message.chat['id'] in self.subscriber_ids:
|
||||||
@@ -53,6 +70,7 @@ class TelegramSmsBot(object):
|
|||||||
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')
|
||||||
|
COMMAND_COUNT.inc()
|
||||||
|
|
||||||
def error_handler(self, update, context):
|
def error_handler(self, update, context):
|
||||||
"""Log Errors caused by Updates."""
|
"""Log Errors caused by Updates."""
|
||||||
@@ -72,6 +90,8 @@ class TelegramSmsBot(object):
|
|||||||
|
|
||||||
def set_owner(self, chat_id):
|
def set_owner(self, chat_id):
|
||||||
self.owner_id = chat_id
|
self.owner_id = chat_id
|
||||||
|
if self.owner_id and self.owner_id not in self.subscriber_ids:
|
||||||
|
self.subscriber_ids.append(self.owner_id)
|
||||||
|
|
||||||
def add_subscriber(self, chat_id):
|
def add_subscriber(self, chat_id):
|
||||||
self.subscriber_ids.append(chat_id)
|
self.subscriber_ids.append(chat_id)
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ import os
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import Flask, abort, current_app, request
|
from flask import Flask, abort, current_app, request
|
||||||
|
from prometheus_client import Counter, Summary, make_wsgi_app
|
||||||
from twilio.request_validator import RequestValidator
|
from twilio.request_validator import RequestValidator
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||||
|
|
||||||
from smsbot.utils import get_smsbot_version
|
from smsbot.utils import get_smsbot_version
|
||||||
|
|
||||||
|
REQUEST_TIME = Summary('webhook_request_processing_seconds', 'Time spent processing request')
|
||||||
|
MESSAGE_COUNT = Counter('webhook_message_count', 'Total number of messages processed')
|
||||||
|
CALL_COUNT = Counter('webhook_call_count', 'Total number of calls processed')
|
||||||
|
|
||||||
|
|
||||||
def validate_twilio_request(func):
|
def validate_twilio_request(func):
|
||||||
"""Validates that incoming requests genuinely originated from Twilio"""
|
"""Validates that incoming requests genuinely originated from Twilio"""
|
||||||
@@ -47,15 +53,26 @@ class TwilioWebhookHandler(object):
|
|||||||
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'])
|
||||||
|
|
||||||
|
# Add prometheus wsgi middleware to route /metrics requests
|
||||||
|
self.app.wsgi_app = DispatcherMiddleware(self.app.wsgi_app, {
|
||||||
|
'/metrics': make_wsgi_app(),
|
||||||
|
})
|
||||||
|
|
||||||
def set_bot(self, bot): # noqa: WPS615
|
def set_bot(self, bot): # noqa: WPS615
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
def index(self):
|
def index(self):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
def health(self):
|
def health(self):
|
||||||
return '<h1>Smsbot v{0}</h1><p><b>Owner</b>: {1}</p><p><b>Subscribers</b>: {2}</p>'.format(get_smsbot_version(), self.bot.owner_id, self.bot.subscriber_ids)
|
return {
|
||||||
|
'version': get_smsbot_version(),
|
||||||
|
'owner': self.bot.owner_id,
|
||||||
|
'subscribers': self.bot.subscriber_ids,
|
||||||
|
}
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
@validate_twilio_request
|
@validate_twilio_request
|
||||||
def message(self):
|
def message(self):
|
||||||
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()))
|
||||||
@@ -64,14 +81,17 @@ class TwilioWebhookHandler(object):
|
|||||||
self.bot.send_subscribers(message)
|
self.bot.send_subscribers(message)
|
||||||
|
|
||||||
# Return a blank response
|
# Return a blank response
|
||||||
|
MESSAGE_COUNT.inc()
|
||||||
return '<response></response>'
|
return '<response></response>'
|
||||||
|
|
||||||
|
@REQUEST_TIME.time()
|
||||||
@validate_twilio_request
|
@validate_twilio_request
|
||||||
def call(self):
|
def call(self):
|
||||||
current_app.logger.info('Received Call from {From}'.format(**request.values.to_dict()))
|
current_app.logger.info('Received Call from {From}'.format(**request.values.to_dict()))
|
||||||
self.bot.send_subscribers('Received Call from {From}, rejecting.'.format(**request.values.to_dict()))
|
self.bot.send_subscribers('Received Call from {From}, rejecting.'.format(**request.values.to_dict()))
|
||||||
|
|
||||||
# Always reject calls
|
# Always reject calls
|
||||||
|
CALL_COUNT.inc()
|
||||||
return '<Response><Reject/></Response>'
|
return '<Response><Reject/></Response>'
|
||||||
|
|
||||||
def serve(self, host='0.0.0.0', port=80, debug=False):
|
def serve(self, host='0.0.0.0', port=80, debug=False):
|
||||||
|
|||||||
Reference in New Issue
Block a user