mirror of
https://github.com/nikdoof/smsbot.git
synced 2025-12-13 01:52:16 +00:00
Support sending SMS
This commit is contained in:
@@ -5,6 +5,7 @@ import os
|
||||
import sys
|
||||
from configparser import ConfigParser
|
||||
from signal import SIGINT, SIGTERM
|
||||
from twilio.rest import Client
|
||||
|
||||
import uvicorn
|
||||
from asgiref.wsgi import WsgiToAsgi
|
||||
@@ -50,15 +51,37 @@ def main():
|
||||
|
||||
# Validate configuration
|
||||
if not config.has_section("telegram") or not config.get("telegram", "bot_token"):
|
||||
logging.error("Telegram bot token is required, define a token either in the config file or as an environment variable.")
|
||||
logging.error(
|
||||
"Telegram bot token is required, define a token either in the config file or as an environment variable."
|
||||
)
|
||||
return
|
||||
|
||||
if config.has_section("twilio") and not (config.get("twilio", "account_sid") and config.get("twilio", "auth_token") and config.get("twilio", "from_number")):
|
||||
logging.error(
|
||||
"Twilio account SID, auth token, and from number are required for outbound SMS functionality, define them in the config file or as environment variables."
|
||||
)
|
||||
return
|
||||
|
||||
# Now the config is loaded, set the logger level
|
||||
level = getattr(logging, config.get("logging", "level", fallback="INFO").upper(), logging.INFO)
|
||||
logging.getLogger().setLevel(level)
|
||||
|
||||
# Configure Twilio client if we have credentials
|
||||
if config.has_section("twilio") and config.get("twilio", "account_sid") and config.get("twilio", "auth_token"):
|
||||
twilio_client = Client(
|
||||
config.get("twilio", "account_sid"),
|
||||
config.get("twilio", "auth_token"),
|
||||
)
|
||||
else:
|
||||
twilio_client = None
|
||||
logging.warning("No Twilio credentials found, outbound SMS functionality will be disabled.")
|
||||
|
||||
# Start bot
|
||||
telegram_bot = TelegramSmsBot(token=config.get("telegram", "bot_token"))
|
||||
telegram_bot = TelegramSmsBot(
|
||||
token=config.get("telegram", "bot_token"),
|
||||
twilio_client=twilio_client,
|
||||
twilio_from_number=config.get("twilio", "from_number", fallback=None),
|
||||
)
|
||||
|
||||
# Set the owner ID if configured
|
||||
if config.has_option("telegram", "owner_id"):
|
||||
@@ -76,7 +99,7 @@ def main():
|
||||
account_sid=config.get("twilio", "account_sid", fallback=None),
|
||||
auth_token=config.get("twilio", "auth_token", fallback=None),
|
||||
)
|
||||
webhooks.set_bot(telegram_bot)
|
||||
webhooks.set_telegram_application(telegram_bot)
|
||||
|
||||
# Build a uvicorn ASGI server
|
||||
flask_app = uvicorn.Server(
|
||||
|
||||
@@ -9,6 +9,7 @@ from telegram.ext import (
|
||||
ContextTypes,
|
||||
TypeHandler,
|
||||
)
|
||||
from twilio.rest import Client
|
||||
|
||||
from smsbot.utils import get_smsbot_version
|
||||
|
||||
@@ -17,11 +18,20 @@ COMMAND_COUNT = Counter("telegram_command_count", "Total number of commands proc
|
||||
|
||||
|
||||
class TelegramSmsBot:
|
||||
def __init__(self, token: str, owners: list[int] = [], subscribers: list[int] = []):
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
twilio_client: Client | None = None,
|
||||
twilio_from_number: str | None = None,
|
||||
owners: list[int] = [],
|
||||
subscribers: list[int] = [],
|
||||
):
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.app = Application.builder().token(token).build()
|
||||
self.owners = owners
|
||||
self.subscribers = subscribers
|
||||
self.twilio_client = twilio_client
|
||||
self.twilio_from_number = twilio_from_number
|
||||
|
||||
self.init_handlers()
|
||||
|
||||
@@ -30,6 +40,7 @@ class TelegramSmsBot:
|
||||
self.app.add_handler(CommandHandler(["help", "start"], self.handler_help))
|
||||
self.app.add_handler(CommandHandler("subscribe", self.handler_subscribe))
|
||||
self.app.add_handler(CommandHandler("unsubscribe", self.handler_unsubscribe))
|
||||
self.app.add_handler(CommandHandler("sms", self.handler_sms))
|
||||
|
||||
async def callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle the update"""
|
||||
@@ -97,3 +108,22 @@ class TelegramSmsBot:
|
||||
await update.message.reply_markdown("You have successfully unsubscribed from updates.")
|
||||
else:
|
||||
self.logger.info(f"User {user_id} is not subscribed.")
|
||||
|
||||
@REQUEST_TIME.time()
|
||||
async def handler_sms(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle sending SMS requests"""
|
||||
if update.effective_user and update.message:
|
||||
user_id = update.effective_user.id
|
||||
if self.twilio_client and self.twilio_from_number:
|
||||
to = context.args[0] if context.args else "No recipient provided"
|
||||
message = context.args[1] if context.args else "No message provided"
|
||||
self.logger.info(f"Sending SMS from user {user_id} -> {to}: {message}")
|
||||
|
||||
try:
|
||||
self.twilio_client.messages.create(body=message, to=to, from_=self.twilio_from_number)
|
||||
except Exception as e:
|
||||
self.logger.exception("Failed to send SMS due to exception")
|
||||
await update.message.reply_markdown("Failed to send SMS")
|
||||
pass
|
||||
else:
|
||||
await update.message.reply_markdown("Twilio client is not configured, cannot send SMS.")
|
||||
|
||||
5
smsbot/utils/__init__.py
Normal file
5
smsbot/utils/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
def get_smsbot_version() -> str:
|
||||
return version("smsbot")
|
||||
@@ -1,10 +1,3 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
def get_smsbot_version() -> str:
|
||||
return version("smsbot")
|
||||
|
||||
|
||||
class TwilioWebhookPayload:
|
||||
@staticmethod
|
||||
def parse(data: dict[str, str]) -> "TwilioCall | TwilioMessage | None":
|
||||
@@ -6,7 +6,8 @@ from prometheus_client import Counter, Summary, make_wsgi_app
|
||||
from twilio.request_validator import RequestValidator
|
||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||
|
||||
from smsbot.utils import TwilioWebhookPayload, get_smsbot_version
|
||||
from smsbot.utils import get_smsbot_version
|
||||
from smsbot.utils.twilio import TwilioWebhookPayload
|
||||
|
||||
REQUEST_TIME = Summary("webhook_request_processing_seconds", "Time spent processing request")
|
||||
MESSAGE_COUNT = Counter("webhook_message_count", "Total number of messages processed")
|
||||
@@ -68,8 +69,9 @@ class TwilioWebhookHandler(object):
|
||||
|
||||
return decorated_function
|
||||
|
||||
def set_bot(self, bot):
|
||||
self.bot = bot
|
||||
def set_telegram_application(self, app):
|
||||
"""Set the Telegram application instance to use for any webhook calls"""
|
||||
self.telegram_app = app
|
||||
|
||||
async def index(self) -> str:
|
||||
return f'smsbot v{get_smsbot_version()} - <a href="https://github.com/nikdoof/smsbot">GitHub</a>'
|
||||
@@ -78,8 +80,8 @@ class TwilioWebhookHandler(object):
|
||||
"""Return basic health information"""
|
||||
return {
|
||||
"version": get_smsbot_version(),
|
||||
"owners": self.bot.owners,
|
||||
"subscribers": len(self.bot.subscribers),
|
||||
"owners": self.telegram_app.owners,
|
||||
"subscribers": len(self.telegram_app.subscribers),
|
||||
}
|
||||
|
||||
@time(REQUEST_TIME)
|
||||
@@ -88,7 +90,7 @@ class TwilioWebhookHandler(object):
|
||||
current_app.logger.info("Received SMS from {From}: {Body}".format(**request.values.to_dict()))
|
||||
hook_data = TwilioWebhookPayload.parse(request.values.to_dict())
|
||||
if hook_data:
|
||||
await self.bot.send_subscribers(hook_data.to_markdownv2())
|
||||
await self.telegram_app.send_subscribers(hook_data.to_markdownv2())
|
||||
|
||||
# Return a blank response
|
||||
MESSAGE_COUNT.inc()
|
||||
@@ -100,7 +102,7 @@ class TwilioWebhookHandler(object):
|
||||
current_app.logger.info("Received Call from {From}".format(**request.values.to_dict()))
|
||||
hook_data = TwilioWebhookPayload.parse(request.values.to_dict())
|
||||
if hook_data:
|
||||
await self.bot.send_subscribers(hook_data.to_markdownv2())
|
||||
await self.telegram_app.send_subscribers(hook_data.to_markdownv2())
|
||||
|
||||
# Always reject calls
|
||||
CALL_COUNT.inc()
|
||||
|
||||
Reference in New Issue
Block a user