├── .gitignore ├── README.md ├── bot ├── __init__.py ├── cogs │ ├── __init__.py │ ├── clicksend_call.py │ ├── ifttt_call.py │ ├── messagebird_call.py │ ├── plivo_call.py │ ├── sinch_call.py │ ├── twilio_call.py │ └── vonage_call.py ├── config.py └── core.py ├── config.json.example ├── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ☎ Discord Phone Call Bot 2 | 3 | A modular Discord bot that can make phone calls using a variety of providers, including IFTTT, Twilio, Vonage, Plivo, ClickSend, MessageBird, and Sinch. Easily configurable and extensible for new integrations. 4 | 5 | ## Features 6 | 7 | - Make phone calls from Discord using multiple providers. 8 | - Modular design: each provider is a separate cog. 9 | - Easy configuration via `config.json`. 10 | - Cooldown and message length limits. 11 | - Python 3.6+ compatible. 12 | 13 | ## Supported Providers & Commands 14 | 15 | | Provider | Command | Description | 16 | |--------------|-------------------|-----------------------------------| 17 | | IFTTT | `!call` | Calls via IFTTT VoIP applet | 18 | | Twilio | `!twilio_call` | Calls using Twilio API | 19 | | Vonage | `!vonage_call` | Calls using Vonage Voice API | 20 | | Plivo | `!plivo_call` | Calls using Plivo Voice API | 21 | | ClickSend | `!clicksend_call` | Calls using ClickSend TTS | 22 | | MessageBird | `!messagebird_call` | Calls using MessageBird Voice | 23 | | Sinch | `!sinch_call` | Calls using Sinch Voice API | 24 | 25 | ## Project Structure 26 | 27 | ``` 28 | . 29 | ├── bot/ 30 | │ ├── __init__.py 31 | │ ├── config.py 32 | │ ├── core.py 33 | │ └── cogs/ 34 | │ ├── __init__.py 35 | │ ├── ifttt_call.py 36 | │ ├── twilio_call.py 37 | │ ├── vonage_call.py 38 | │ ├── plivo_call.py 39 | │ ├── clicksend_call.py 40 | │ ├── messagebird_call.py 41 | │ └── sinch_call.py 42 | ├── config.json 43 | ├── config.json.example 44 | ├── requirements.txt 45 | ├── main.py 46 | └── README.md 47 | ``` 48 | 49 | ## Setup 50 | 51 | 1. **Clone the repository and install dependencies:** 52 | ```sh 53 | pip install -r requirements.txt 54 | ``` 55 | 56 | 2. **Configure your bot:** 57 | - Copy `config.json.example` to `config.json`. 58 | - Fill in the required fields for the providers you want to use. 59 | - You must always set up your Discord bot token and a command prefix. 60 | 61 | 3. **Provider Setup:** 62 | - For each provider, follow their documentation to obtain API keys, tokens, and phone numbers. 63 | - See comments in `config.json.example` for required fields. 64 | 65 | 4. **Run the bot:** 66 | ```sh 67 | python main.py 68 | ``` 69 | 70 | ## Example `config.json` 71 | 72 | See `config.json.example` for all available options and documentation. 73 | 74 | ## Adding New Integrations 75 | 76 | - Add a new cog in `bot/cogs/`. 77 | - Document new config fields in `config.json.example`. 78 | - Update this README with the new command. 79 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | # Bot package initializer 2 | -------------------------------------------------------------------------------- /bot/cogs/__init__.py: -------------------------------------------------------------------------------- 1 | # Cogs package initializer 2 | -------------------------------------------------------------------------------- /bot/cogs/clicksend_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | import clicksend_client 4 | from clicksend_client import VoiceApi, SmsMessage, VoiceMessage 5 | from clicksend_client.rest import ApiException 6 | 7 | class ClickSendCallCog(commands.Cog): 8 | def __init__(self, bot): 9 | self.bot = bot 10 | cfg = bot.config 11 | self.username = cfg.get("clicksend_username") 12 | self.api_key = cfg.get("clicksend_api_key") 13 | self.to_number = cfg.get("clicksend_to_number") 14 | self.voice = cfg.get("clicksend_voice", "female") 15 | self.language = cfg.get("clicksend_language", "en-us") 16 | self.enabled = all([self.username, self.api_key, self.to_number]) 17 | if self.enabled: 18 | configuration = clicksend_client.Configuration() 19 | configuration.username = self.username 20 | configuration.password = self.api_key 21 | self.voice_api = VoiceApi(clicksend_client.ApiClient(configuration)) 22 | else: 23 | self.voice_api = None 24 | 25 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 26 | @commands.command(name="clicksend_call") 27 | async def clicksend_call(self, ctx, *, message): 28 | if not self.enabled: 29 | await ctx.send("ClickSend integration is not configured. Please set up the required fields in config.json.") 30 | return 31 | 32 | max_length = int(self.bot.config["maxMsgLength"]) 33 | if len(message) > max_length: 34 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 35 | return 36 | 37 | voice_message = VoiceMessage( 38 | to=self.to_number, 39 | body=message, 40 | voice=self.voice, 41 | lang=self.language 42 | ) 43 | try: 44 | api_response = self.voice_api.voice_send_post(voice_message) 45 | await ctx.send("**ClickSend call initiated! :telephone_receiver:**") 46 | except ApiException as e: 47 | await ctx.send(f"Failed to initiate ClickSend call: {e}") 48 | 49 | def setup(bot): 50 | bot.add_cog(ClickSendCallCog(bot)) 51 | -------------------------------------------------------------------------------- /bot/cogs/ifttt_call.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.ext.commands.cooldowns import BucketType 4 | import aiohttp 5 | 6 | class IFTTTCallCog(commands.Cog): 7 | def __init__(self, bot): 8 | self.bot = bot 9 | 10 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 11 | @commands.command(name="call") 12 | async def call(self, ctx, *, message): 13 | max_length = int(self.bot.config["maxMsgLength"]) 14 | if len(message) <= max_length: 15 | report = { 16 | "value1": f"New message. {message}", 17 | "value2": f". Sent by {ctx.author.name} on the server {ctx.guild.name}." 18 | } 19 | async with aiohttp.ClientSession() as session: 20 | await session.post( 21 | f"https://maker.ifttt.com/trigger/{self.bot.config['eventName']}/with/key/{self.bot.config['IFTTTkey']}", 22 | data=report 23 | ) 24 | await ctx.send("**Data posted! Calling the bot owner now :telephone_receiver:**") 25 | else: 26 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 27 | 28 | def setup(bot): 29 | bot.add_cog(IFTTTCallCog(bot)) 30 | -------------------------------------------------------------------------------- /bot/cogs/messagebird_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | import messagebird 4 | 5 | class MessageBirdCallCog(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | cfg = bot.config 9 | self.access_key = cfg.get("messagebird_access_key") 10 | self.from_number = cfg.get("messagebird_from_number") 11 | self.to_number = cfg.get("messagebird_to_number") 12 | self.voice = cfg.get("messagebird_voice", "female") 13 | self.language = cfg.get("messagebird_language", "en-us") 14 | self.enabled = all([self.access_key, self.from_number, self.to_number]) 15 | if self.enabled: 16 | self.client = messagebird.Client(self.access_key) 17 | else: 18 | self.client = None 19 | 20 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 21 | @commands.command(name="messagebird_call") 22 | async def messagebird_call(self, ctx, *, message): 23 | if not self.enabled: 24 | await ctx.send("MessageBird integration is not configured. Please set up the required fields in config.json.") 25 | return 26 | 27 | max_length = int(self.bot.config["maxMsgLength"]) 28 | if len(message) > max_length: 29 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 30 | return 31 | 32 | try: 33 | call_flow = self.client.voice_create_call( 34 | source=self.from_number, 35 | destination=self.to_number, 36 | callflow={ 37 | "title": "DiscordBotCall", 38 | "steps": [ 39 | { 40 | "action": "say", 41 | "options": { 42 | "payload": message, 43 | "voice": self.voice, 44 | "language": self.language 45 | } 46 | } 47 | ] 48 | } 49 | ) 50 | call_id = getattr(call_flow, 'id', 'unknown') 51 | await ctx.send(f"**MessageBird call initiated! Call ID: {call_id} :telephone_receiver:**") 52 | except Exception as e: 53 | await ctx.send(f"Failed to initiate MessageBird call: {e}") 54 | 55 | def setup(bot): 56 | bot.add_cog(MessageBirdCallCog(bot)) 57 | -------------------------------------------------------------------------------- /bot/cogs/plivo_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | import plivo 4 | 5 | class PlivoCallCog(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | cfg = bot.config 9 | self.auth_id = cfg.get("plivo_auth_id") 10 | self.auth_token = cfg.get("plivo_auth_token") 11 | self.from_number = cfg.get("plivo_from_number") 12 | self.to_number = cfg.get("plivo_to_number") 13 | self.enabled = all([self.auth_id, self.auth_token, self.from_number, self.to_number]) 14 | if self.enabled: 15 | self.client = plivo.RestClient(self.auth_id, self.auth_token) 16 | else: 17 | self.client = None 18 | 19 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 20 | @commands.command(name="plivo_call") 21 | async def plivo_call(self, ctx, *, message): 22 | if not self.enabled: 23 | await ctx.send("Plivo integration is not configured. Please set up the required fields in config.json.") 24 | return 25 | 26 | max_length = int(self.bot.config["maxMsgLength"]) 27 | if len(message) > max_length: 28 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 29 | return 30 | 31 | answer_url = f"https://s3.amazonaws.com/plivocloud/PlivoTTS.xml?text={message}" 32 | try: 33 | response = self.client.calls.create( 34 | from_=self.from_number, 35 | to=self.to_number, 36 | answer_url=answer_url, 37 | answer_method='GET' 38 | ) 39 | call_id = getattr(response, 'request_uuid', 'unknown') 40 | await ctx.send(f"**Plivo call initiated! Call UUID: {call_id} :telephone_receiver:**") 41 | except Exception as e: 42 | await ctx.send(f"Failed to initiate Plivo call: {e}") 43 | 44 | def setup(bot): 45 | bot.add_cog(PlivoCallCog(bot)) 46 | -------------------------------------------------------------------------------- /bot/cogs/sinch_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | from sinch import SinchClient 4 | 5 | class SinchCallCog(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | cfg = bot.config 9 | self.key_id = cfg.get("sinch_key_id") 10 | self.key_secret = cfg.get("sinch_key_secret") 11 | self.from_number = cfg.get("sinch_from_number") 12 | self.to_number = cfg.get("sinch_to_number") 13 | self.enabled = all([self.key_id, self.key_secret, self.from_number, self.to_number]) 14 | if self.enabled: 15 | self.client = SinchClient(key_id=self.key_id, key_secret=self.key_secret) 16 | else: 17 | self.client = None 18 | 19 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 20 | @commands.command(name="sinch_call") 21 | async def sinch_call(self, ctx, *, message): 22 | if not self.enabled: 23 | await ctx.send("Sinch integration is not configured. Please set up the required fields in config.json.") 24 | return 25 | 26 | max_length = int(self.bot.config["maxMsgLength"]) 27 | if len(message) > max_length: 28 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 29 | return 30 | 31 | try: 32 | response = self.client.voice.callouts.tts( 33 | to=self.to_number, 34 | from_=self.from_number, 35 | text=message 36 | ) 37 | call_id = response.get("callId", "unknown") 38 | await ctx.send(f"**Sinch call initiated! Call ID: {call_id} :telephone_receiver:**") 39 | except Exception as e: 40 | await ctx.send(f"Failed to initiate Sinch call: {e}") 41 | 42 | def setup(bot): 43 | bot.add_cog(SinchCallCog(bot)) 44 | -------------------------------------------------------------------------------- /bot/cogs/twilio_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | from twilio.rest import Client 4 | 5 | class TwilioCallCog(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | cfg = bot.config 9 | self.twilio_sid = cfg.get("twilio_account_sid") 10 | self.twilio_token = cfg.get("twilio_auth_token") 11 | self.from_number = cfg.get("twilio_from_number") 12 | self.to_number = cfg.get("twilio_to_number") 13 | self.enabled = all([self.twilio_sid, self.twilio_token, self.from_number, self.to_number]) 14 | 15 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 16 | @commands.command(name="twilio_call") 17 | async def twilio_call(self, ctx, *, message): 18 | if not self.enabled: 19 | await ctx.send("Twilio integration is not configured. Please set up the required fields in config.json.") 20 | return 21 | 22 | max_length = int(self.bot.config["maxMsgLength"]) 23 | if len(message) > max_length: 24 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 25 | return 26 | 27 | client = Client(self.twilio_sid, self.twilio_token) 28 | try: 29 | call = client.calls.create( 30 | twiml=f'{message}', 31 | to=self.to_number, 32 | from_=self.from_number 33 | ) 34 | await ctx.send(f"**Twilio call initiated! Call SID: {call.sid} :telephone_receiver:**") 35 | except Exception as e: 36 | await ctx.send(f"Failed to initiate Twilio call: {e}") 37 | 38 | def setup(bot): 39 | bot.add_cog(TwilioCallCog(bot)) 40 | -------------------------------------------------------------------------------- /bot/cogs/vonage_call.py: -------------------------------------------------------------------------------- 1 | from discord.ext import commands 2 | from discord.ext.commands.cooldowns import BucketType 3 | import vonage 4 | 5 | class VonageCallCog(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | cfg = bot.config 9 | self.api_key = cfg.get("vonage_api_key") 10 | self.api_secret = cfg.get("vonage_api_secret") 11 | self.from_number = cfg.get("vonage_from_number") 12 | self.to_number = cfg.get("vonage_to_number") 13 | self.enabled = all([self.api_key, self.api_secret, self.from_number, self.to_number]) 14 | if self.enabled: 15 | self.client = vonage.Client(key=self.api_key, secret=self.api_secret) 16 | self.voice = vonage.Voice(self.client) 17 | else: 18 | self.client = None 19 | self.voice = None 20 | 21 | @commands.cooldown(1, property(lambda self: int(self.bot.config["callCoolDown"])), BucketType.user) 22 | @commands.command(name="vonage_call") 23 | async def vonage_call(self, ctx, *, message): 24 | if not self.enabled: 25 | await ctx.send("Vonage integration is not configured. Please set up the required fields in config.json.") 26 | return 27 | 28 | max_length = int(self.bot.config["maxMsgLength"]) 29 | if len(message) > max_length: 30 | await ctx.send(f"**{ctx.author.mention} You can't send messages over {max_length} chars long. :no_entry:**") 31 | return 32 | 33 | ncco = [ 34 | { 35 | "action": "talk", 36 | "voiceName": "Joanna", 37 | "text": message 38 | } 39 | ] 40 | try: 41 | response = self.voice.create_call({ 42 | "to": [{"type": "phone", "number": self.to_number}], 43 | "from": {"type": "phone", "number": self.from_number}, 44 | "ncco": ncco 45 | }) 46 | call_id = response.get("uuid", "unknown") 47 | await ctx.send(f"**Vonage call initiated! Call UUID: {call_id} :telephone_receiver:**") 48 | except Exception as e: 49 | await ctx.send(f"Failed to initiate Vonage call: {e}") 50 | 51 | def setup(bot): 52 | bot.add_cog(VonageCallCog(bot)) 53 | -------------------------------------------------------------------------------- /bot/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | class ConfigError(Exception): 5 | pass 6 | 7 | def load_config(config_path=None): 8 | """ 9 | Loads and validates the configuration from config.json. 10 | If config_path is not provided, defaults to ../config.json relative to this file. 11 | Raises ConfigError if required fields are missing. 12 | """ 13 | if config_path is None: 14 | base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 15 | config_path = os.path.join(base_dir, "config.json") 16 | with open(config_path, "r") as f: 17 | config = json.load(f) 18 | required_fields = [ 19 | "prefix", "callCoolDown", "maxMsgLength", 20 | "eventName", "IFTTTkey", "discordToken" 21 | ] 22 | missing = [field for field in required_fields if field not in config] 23 | if missing: 24 | raise ConfigError(f"Missing required config fields: {', '.join(missing)}") 25 | return config 26 | -------------------------------------------------------------------------------- /bot/core.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import discord 3 | from discord.ext import commands 4 | from bot.config import load_config, ConfigError 5 | 6 | def setup_logging(): 7 | logging.basicConfig( 8 | level=logging.INFO, 9 | format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" 10 | ) 11 | 12 | def create_bot(): 13 | try: 14 | config = load_config() 15 | except ConfigError as e: 16 | logging.error(f"Configuration error: {e}") 17 | raise SystemExit(1) 18 | 19 | intents = discord.Intents.default() 20 | intents.message_content = True 21 | 22 | bot = commands.Bot( 23 | command_prefix=commands.when_mentioned_or(config["prefix"]), 24 | case_insensitive=True, 25 | intents=intents 26 | ) 27 | bot.config = config 28 | 29 | @bot.event 30 | async def on_ready(): 31 | logging.info("=========") 32 | logging.info("Ready for use!") 33 | logging.info("Bot created by https://github.com/SpectrixDev") 34 | logging.info("=========") 35 | 36 | @bot.event 37 | async def on_command_error(ctx, error): 38 | if isinstance(error, commands.CommandOnCooldown): 39 | await ctx.send(f"**:no_entry: {error}**") 40 | else: 41 | logging.error(f"Unhandled command error: {error}") 42 | 43 | # Load all cogs from bot/cogs/ 44 | import os 45 | import glob 46 | 47 | cogs_dir = os.path.join(os.path.dirname(__file__), "cogs") 48 | for cog_file in glob.glob(os.path.join(cogs_dir, "*.py")): 49 | if not cog_file.endswith("__init__.py"): 50 | cog_name = f"bot.cogs.{os.path.splitext(os.path.basename(cog_file))[0]}" 51 | try: 52 | bot.load_extension(cog_name) 53 | logging.info(f"Loaded cog: {cog_name}") 54 | except Exception as e: 55 | logging.error(f"Failed to load cog {cog_name}: {e}") 56 | 57 | return bot 58 | 59 | def run(): 60 | setup_logging() 61 | bot = create_bot() 62 | bot.run(bot.config["discordToken"]) 63 | -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": String: Used to "talk to" your bot. You can also mention the bot by default to run commands, but you also need a prefix. 3 | "callCoolDown": Integer: Set a cooldown between how long users can use the call command. I recommend 30 seconds or more (this is per user) 4 | "maxMsgLength": Integer: The max amount of characters for the message to be read aloud over the phone call. Should be less than 2000, I recommend 250 5 | "eventName": String: The event name from your webhook 6 | "IFTTTkey": String: The IFTTT token/key that you got from the webhook settings URL thingy 7 | "discordToken": String: Your bot token from the Discord developer portal in the bot section (NOT client secret/client ID), 8 | 9 | // Twilio integration fields: 10 | "twilio_account_sid": String: Your Twilio Account SID, 11 | "twilio_auth_token": String: Your Twilio Auth Token, 12 | "twilio_from_number": String: The Twilio phone number to send calls from (in E.164 format, e.g. "+1234567890"), 13 | "twilio_to_number": String: The phone number to call (in E.164 format, e.g. "+1234567890"), 14 | 15 | // Vonage integration fields: 16 | "vonage_api_key": String: Your Vonage API Key, 17 | "vonage_api_secret": String: Your Vonage API Secret, 18 | "vonage_application_id": String: Your Vonage Application ID (optional), 19 | "vonage_private_key_path": String: Path to your Vonage private.key (optional), 20 | "vonage_from_number": String: The Vonage phone number to send calls from, 21 | "vonage_to_number": String: The phone number to call, 22 | 23 | // Plivo integration fields: 24 | "plivo_auth_id": String: Your Plivo Auth ID, 25 | "plivo_auth_token": String: Your Plivo Auth Token, 26 | "plivo_from_number": String: The Plivo phone number to send calls from, 27 | "plivo_to_number": String: The phone number to call, 28 | 29 | // ClickSend integration fields: 30 | "clicksend_username": String: Your ClickSend username, 31 | "clicksend_api_key": String: Your ClickSend API key, 32 | "clicksend_to_number": String: The phone number to call, 33 | "clicksend_voice": String: Voice type (e.g., "female", "male"), 34 | "clicksend_language": String: Language code (e.g., "en-us", "en-gb"), 35 | 36 | // MessageBird integration fields: 37 | "messagebird_access_key": String: Your MessageBird Access Key, 38 | "messagebird_from_number": String: The MessageBird number or alphanumeric sender, 39 | "messagebird_to_number": String: The phone number to call, 40 | "messagebird_voice": String: Voice type (e.g., "female", "male"), 41 | "messagebird_language": String: Language code (e.g., "en-us"), 42 | 43 | // Sinch integration fields: 44 | "sinch_key_id": String: Your Sinch Key ID, 45 | "sinch_key_secret": String: Your Sinch Key Secret, 46 | "sinch_from_number": String: The Sinch phone number to send calls from, 47 | "sinch_to_number": String: The phone number to call 48 | } 49 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from bot.core import run 2 | 3 | if __name__ == "__main__": 4 | run() 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.11.0b0 2 | asyncio==3.4.3 3 | discord.py==1.7.3 4 | twilio==8.10.0 5 | vonage==3.8.0 6 | plivo==4.30.0 7 | clicksend-client==5.0.37 8 | messagebird==1.5.0 9 | sinch-sdk==0.10.0 10 | --------------------------------------------------------------------------------