Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
a5a5b2c7de | |||
259fbf3e1e | |||
0ced89e88a | |||
f3f6d2d2ea | |||
2d0b08975f | |||
1bb6fdb032 | |||
2669aef9c2 | |||
9b9e161098 | |||
bc6e15b8c8 | |||
2204b088bd | |||
1f8688d8a6 | |||
aa63d3c781 | |||
42f2016482 | |||
fc3b530ff5 | |||
cf4585e647 | |||
40df1726da | |||
3168c77ba7 | |||
8e1e5c1aa5 | |||
97220407f1 | |||
dd672053f1 | |||
99adfa7384 | |||
5ab98b1b0c | |||
585094b818 | |||
9438a24131 | |||
c8cb8746b3 |
6 changed files with 73 additions and 27 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
# Internal
|
# Internal
|
||||||
.env
|
*.env
|
||||||
|
|
|
@ -5,7 +5,9 @@ RUN apt-get -yq install ffmpeg build-essential
|
||||||
|
|
||||||
RUN pip --no-cache-dir install \
|
RUN pip --no-cache-dir install \
|
||||||
discord.py[voice] \
|
discord.py[voice] \
|
||||||
pynacl
|
pynacl \
|
||||||
|
httplib2 \
|
||||||
|
urllib3
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
|
|
||||||
* Live radio re-streaming to voice channels.
|
* Live radio re-streaming to voice channels.
|
||||||
* Automatically turned on/off when someone joins/leaves the bound channel.
|
* Automatically turned on/off when someone joins/leaves the bound channel.
|
||||||
|
* Shows ICY StreamTitle
|
||||||
* Other cool things.
|
* Other cool things.
|
||||||
|
|
||||||
## Running using Docker from the repo
|
## Running using Docker from the repo
|
||||||
|
|
||||||
1. `sudo apt install docker-compose`
|
1. `sudo apt install docker-compose`
|
||||||
2. `cp .env.dist .env`
|
2. `cp radiobot.env.dist radiobot.env`
|
||||||
3. `vim .env`
|
3. `vim radiobot.env`
|
||||||
4. `docker-compose up -d --build --remove-orphans`
|
4. `docker-compose up -d --build --remove-orphans`
|
||||||
5. `docker-compose logs --follow --tail 100`
|
5. `docker-compose logs --follow --tail 100 --timestamps`
|
||||||
|
|
|
@ -6,6 +6,6 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- radiobot.env
|
||||||
command: python3 -u main.py
|
command: python3 -u main.py
|
||||||
restart: always
|
restart: always
|
||||||
|
|
82
main.py
82
main.py
|
@ -1,28 +1,31 @@
|
||||||
import os
|
import os
|
||||||
import discord
|
import sys
|
||||||
from discord.ext import commands
|
import re
|
||||||
|
import struct
|
||||||
import asyncio
|
import asyncio
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
bot_version = os.environ['version']
|
import discord
|
||||||
print('radiobot ' + bot_version + ' starting')
|
from discord.ext import commands
|
||||||
|
import urllib.request as urllib2
|
||||||
|
|
||||||
login_token = os.environ['token']
|
login_token = os.environ['token']
|
||||||
voice_channel_id = os.environ['channel_voice']
|
voice_channel_id = os.environ['channel_voice']
|
||||||
text_channel_id = os.environ['channel_text']
|
text_channel_id = os.environ['channel_text']
|
||||||
|
source = os.environ['url']
|
||||||
|
|
||||||
# Configure the bot
|
# Configure the bot
|
||||||
description = '''Radiobot'''
|
description = '''Radiobot'''
|
||||||
bot = commands.Bot(command_prefix='!', description=description)
|
bot = commands.Bot(command_prefix='!', description=description)
|
||||||
|
|
||||||
# Initialize opus
|
|
||||||
#if not discord.opus.is_loaded():
|
|
||||||
# discord.opus.load_opus('opus')
|
|
||||||
|
|
||||||
# Initialize some global variables
|
# Initialize some global variables
|
||||||
voice_client = None
|
voice_client = None
|
||||||
text_channel = None
|
text_channel = None
|
||||||
isConnected = False
|
isConnected = False
|
||||||
|
encoding = 'latin1'
|
||||||
|
bot_version = '0.2'
|
||||||
|
|
||||||
|
print('[INFO] radiobot ' + bot_version + ' starting')
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
|
@ -35,8 +38,7 @@ async def on_ready():
|
||||||
print("[WARN] No voice channel " + voice_channel_id + " found!")
|
print("[WARN] No voice channel " + voice_channel_id + " found!")
|
||||||
if not debug_channel:
|
if not debug_channel:
|
||||||
print("[WARN] No text channel " + text_channel_id + " found!")
|
print("[WARN] No text channel " + text_channel_id + " found!")
|
||||||
|
await debug_channel.send('] ready! :satellite_orbital:')
|
||||||
await debug_channel.send('] ready.')
|
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
|
@ -49,8 +51,31 @@ async def on_message(message):
|
||||||
|
|
||||||
print('<' + message.author.nick + '> ' + message.content)
|
print('<' + message.author.nick + '> ' + message.content)
|
||||||
|
|
||||||
|
if message.content == '!help':
|
||||||
|
await message.channel.send('] radiobot commands: help, version, song :star:')
|
||||||
|
|
||||||
if message.content == '!version':
|
if message.content == '!version':
|
||||||
await message.channel.send('] radiobot ' + bot_version + ' - python ' + os.environ['PYTHON_VERSION'])
|
await message.channel.send('] radiobot ' + bot_version + ' - python ' + os.environ['PYTHON_VERSION'] + ' - github.com/deflax/radiobot :purple_heart:')
|
||||||
|
|
||||||
|
if message.content == '!song':
|
||||||
|
request = urllib2.Request(source, headers={'Icy-MetaData': 1}) # request metadata
|
||||||
|
response = urllib2.urlopen(request)
|
||||||
|
metaint = int(response.headers['icy-metaint'])
|
||||||
|
for _ in range(10): # # title may be empty initially, try several times
|
||||||
|
response.read(metaint) # skip to metadata
|
||||||
|
metadata_length = struct.unpack('B', response.read(1))[0] * 16 # length byte
|
||||||
|
metadata = response.read(metadata_length).rstrip(b'\0')
|
||||||
|
m = re.search(br"StreamTitle='([^']*)';", metadata)
|
||||||
|
if m:
|
||||||
|
title = m.group(1)
|
||||||
|
if title:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('no title found')
|
||||||
|
return
|
||||||
|
meta = title.decode(encoding, errors='replace')
|
||||||
|
metasplit = meta.split('*', 1)[0]
|
||||||
|
await message.channel.send('] ' + metasplit + ' :musical_note:')
|
||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_voice_state_update(member, before, after):
|
async def on_voice_state_update(member, before, after):
|
||||||
|
@ -67,28 +92,49 @@ async def on_voice_state_update(member, before, after):
|
||||||
"""
|
"""
|
||||||
global isConnected
|
global isConnected
|
||||||
FFMPEG_OPTS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
|
FFMPEG_OPTS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
|
||||||
|
member_msg = None
|
||||||
|
|
||||||
if member.bot:
|
if member.bot:
|
||||||
#print("[INFO] self event detection")
|
#print("[INFO] self event detection")
|
||||||
return
|
return
|
||||||
|
|
||||||
voice_channel = await bot.fetch_channel(voice_channel_id)
|
|
||||||
debug_channel = await bot.fetch_channel(text_channel_id)
|
debug_channel = await bot.fetch_channel(text_channel_id)
|
||||||
|
voice_channel = await bot.fetch_channel(voice_channel_id)
|
||||||
member_ids = len(voice_channel.voice_states.keys())
|
member_ids = len(voice_channel.voice_states.keys())
|
||||||
|
|
||||||
#await debug_channel.send('] voice #' + voice_channel_id + ' member count: ' + str(member_ids))
|
if before.channel is None:
|
||||||
|
prev_chan = "not_found"
|
||||||
|
else:
|
||||||
|
prev_chan = str(before.channel.id)
|
||||||
|
#if prev_chan == str(voice_channel_id):
|
||||||
|
# member_msg = str(member.nick) + ' is back in the void :cyclone:'
|
||||||
|
|
||||||
if member_ids == 1 and isConnected == False:
|
if after.channel is None:
|
||||||
|
next_chan = "not_found"
|
||||||
|
else:
|
||||||
|
next_chan = str(after.channel.id)
|
||||||
|
if next_chan == str(voice_channel_id):
|
||||||
|
member_msg = str(member.nick) + ' enjoys! :satellite:'
|
||||||
|
|
||||||
|
if prev_chan == next_chan:
|
||||||
|
#print('[INFO] ' + str(member.nick) + ' activity')
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if member_msg is not None:
|
||||||
|
print('[INFO] ' + member_msg)
|
||||||
|
await debug_channel.send('] ' + member_msg)
|
||||||
|
|
||||||
|
if member_ids > 0 and isConnected == False:
|
||||||
isConnected = True
|
isConnected = True
|
||||||
await debug_channel.send('] connecting to #' + voice_channel_id)
|
|
||||||
voice_client = await voice_channel.connect()
|
voice_client = await voice_channel.connect()
|
||||||
player = voice_client.play(discord.FFmpegPCMAudio(os.environ['url'], **FFMPEG_OPTS))
|
voice_client.play(discord.FFmpegPCMAudio(source, **FFMPEG_OPTS))
|
||||||
|
voice_client.source = discord.PCMVolumeTransformer(voice_client.source)
|
||||||
|
voice_client.source.volume = 0.3
|
||||||
return
|
return
|
||||||
|
|
||||||
if member_ids == 1 and isConnected == True:
|
if member_ids == 1 and isConnected == True:
|
||||||
isConnected = False
|
isConnected = False
|
||||||
await debug_channel.send('] disconnecting from #' + voice_channel_id)
|
#await debug_channel.send('] sleeping. :satellite_orbital:')
|
||||||
for voice_client in bot.voice_clients:
|
for voice_client in bot.voice_clients:
|
||||||
await voice_client.disconnect()
|
await voice_client.disconnect()
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
version=0.1
|
|
||||||
token=OT..
|
token=OT..
|
||||||
channel_text=123
|
channel_text=123
|
||||||
channel_voice=456
|
channel_voice=456
|
||||||
url=http://example.net
|
url=http://example.net
|
||||||
passes=2
|
|
||||||
bitrate=48000
|
|
Loading…
Add table
Reference in a new issue