-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
143 lines (121 loc) · 5.09 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import asyncio
import discord
import os
import yaml
from config import Settings
from audiosource import FFmpegRTPSource, FFmpegRTPSink, stub_callback
from pyVoIP.VoIP import VoIPPhone, InvalidStateError, CallState, VoIPCall
if os.environ.get("VOIPCORD_ENVCONFIG"):
settings = Settings() # Exclusively use environment variables for configuration.
elif os.path.exists(os.environ.get('CONFIG', "config.yml")):
config = yaml.safe_load(open(os.environ.get('CONFIG', "config.yml"), encoding="utf8"))
settings = Settings.parse_obj(config)
else:
print(f"No config file was found at {os.environ.get('CONFIG', 'config.yml')}, failing over to environment "
"variables.\nIf this was intentional, set VOIPCORD_ENVCONFIG=true to hide this warning.")
settings = Settings()
print('VoIPcord\nhttps://github.com/samicrusader/voipcord', end='\n--\n')
loop = asyncio.new_event_loop()
intents = discord.Intents.default()
intents.members = True
intents.messages = True
intents.message_content = True
def incoming_stub(call: VoIPCall):
future = asyncio.run_coroutine_threadsafe(incoming(call), loop)
return future.result()
client = discord.Bot(test_guilds=[settings.discord.home_guild_id], intents=intents, prefix='!', loop=loop)
phone = VoIPPhone(server=settings.voip.server, port=settings.voip.port, username=settings.voip.username,
password=settings.voip.password, callCallback=incoming_stub)
voip_commands = client.create_group('phone', 'Telephony commands')
mgmt_commands = client.create_group('voipcord', 'Management commands')
connections = {}
calls = {}
@client.event
async def on_ready():
print(f'Logged in as {client.user.name}#{client.user.discriminator} ({client.user.id})')
await asyncio.to_thread(phone.start)
print(phone.get_status())
print(await client.sync_commands())
@client.event
async def on_application_command_error(ctx: discord.ApplicationContext, error):
await ctx.respond(str(error), ephemeral=True)
raise error
@voip_commands.command(name='call', description='call number', ephemeral=True,
guild_ids=[settings.discord.home_guild_id])
async def dial(ctx, number: discord.Option(discord.SlashCommandOptionType.string)):
# setup voice
if not ctx.author.voice:
return await ctx.respond('join the vc you fuckwit', ephemeral=True)
vc = await ctx.author.voice.channel.connect()
connections.update({ctx.guild.id: vc})
# dial number
call = await asyncio.to_thread(phone.call, number)
await ctx.defer(ephemeral=True) # tell discord we'll be a minute
# wait for call answer or fail
while True:
await asyncio.sleep(0.1)
if call.state == CallState.ENDED:
await ctx.respond('Call failed.', ephemeral=True)
return
if call.state == CallState.ANSWERED:
break
# caller answered, shit out call state and setup audio
try:
await ctx.respond('Call answered!', ephemeral=True)
vc.start_recording(FFmpegRTPSink(call), stub_callback, ctx.channel)
source = FFmpegRTPSource(source=call)
vc.play(source)
calls.update({ctx.guild.id: call})
while call.state == CallState.ANSWERED:
await asyncio.sleep(0.1)
except InvalidStateError as e:
await ctx.respond('Caller disconnected.', ephemeral=True)
return
async def incoming(call: VoIPCall):
channel = client.get_channel(settings.discord.default_text_channel_id)
await channel.send('incoming call, join vc in 5s')
await asyncio.sleep(5)
# setup voice
vc = client.get_channel(settings.discord.default_voice_channel_id)
if not vc.members:
await channel.send('hanging up')
await asyncio.to_thread(call.deny)
vc = await vc.connect()
connections.update({vc.guild.id: vc})
# caller answered, shit out call state and setup audio
await asyncio.to_thread(call.answer)
try:
await channel.send('Call answered!')
vc.start_recording(FFmpegRTPSink(call), stub_callback, vc)
source = FFmpegRTPSource(source=call)
vc.play(source)
calls.update({vc.guild.id: call})
while call.state == CallState.ANSWERED:
await asyncio.sleep(0.1)
except InvalidStateError as e:
await channel.send('Caller disconnected.')
return
@voip_commands.command(name='hook', description='hang up phone', ephemeral=True,
guild_ids=[settings.discord.home_guild_id])
async def hangup(ctx):
if ctx.guild.id not in calls.keys():
return await ctx.respond('Phone is not engaged in a call.', ephemeral=True)
vc: discord.VoiceClient = connections[ctx.guild.id]
call: VoIPCall = calls[ctx.guild.id]
vc.stop_recording()
vc.stop()
try:
call.hangup()
except InvalidStateError:
await ctx.respond('Phone was already on-hook.', ephemeral=True)
return
else:
return await ctx.respond('Phone is now on-hook.', ephemeral=True)
finally:
del connections[ctx.guild.id]
await vc.disconnect(force=True)
try:
client.run(settings.discord.token)
except:
client.close()
phone.stop()