Skip to content

Instantly share code, notes, and snippets.

@twilight-sparkle-irl
Last active January 29, 2021 22:14
Show Gist options
  • Save twilight-sparkle-irl/dfa864e823aa482d904d6bfd1f5b43b8 to your computer and use it in GitHub Desktop.
Save twilight-sparkle-irl/dfa864e823aa482d904d6bfd1f5b43b8 to your computer and use it in GitHub Desktop.
simple LiquidSoap Discord bot
import client
import discord
from discord.ext import commands
# TODO: Automatic updates on the statuses of tracks
# TODO: Make %info command make more sense and only let you view the status of requested tracks
# TODO: Make %queue and %info possible for non-DJs by ensuring safe commands — currently it's a security risk(?)
description = '''What's up, fresh meat. It's Vinyl Scratch, bringing you the tunes YOU want and letting you know what's under the needle.'''
bot = commands.Bot(command_prefix='%', description=description)
print("and 1")
cli = client.LiqClient('localhost',1234, "root","request")
print("and 2")
@bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
@bot.command()
@commands.has_role("DJ")
async def skip(ctx):
skipped = cli.skip()
if skipped:
await ctx.send("Skipped that song.")
else: await ctx.send("Something went wrong.")
@bot.command()
@commands.has_role("DJ")
async def queue(ctx, uri):
x = cli.queue(uri)
await ctx.send("Your number is {0}. Do `%info {0}` to check your track's status.".format(x))
@bot.command()
@commands.has_role("DJ")
async def info(ctx, number):
i = cli.info(number)
if not i or len(i) > 1:
await ctx.send("No such number?")
return
i = i[0]
title = i['title'] if 'title' in i else i['initial_uri']
if i['status'] == "destroyed":
description = "Status is 'destroyed'. Did you input a valid URL?"
elif i['status'] == "resolving":
description = "Downloading this track right now! Wait warmly!"
elif i['status'] == "ready":
description = "Track is ready! Wait for it..."
elif i['status'] == "playing":
description = "Song's playing."
else: description = "Song is {}.".format(i['status'])
embed = discord.Embed(title=title, description=description, color=0x3167CC)
await ctx.send(embed=embed)
@bot.command()
async def np(ctx):
playing = cli.np()
title = playing['title'] if 'title' in playing else playing['filename']
if 'purl' in playing:
orig = playing['purl']
elif 'initial_uri' in playing:
orig = playing['initial_uri'].replace('youtube-dl:','')
else:
orig = "(unknown original URL)"
embed = discord.Embed(title=title, description=orig, color=0x3167CC)
await ctx.send(embed=embed)
if __name__ == "__main__":
print("startin")
bot.run("INSERT TOKEN HERE")
#!/usr/bin/python
from telnetlib import Telnet
import re
import pprint
class LiqBaseClient:
def __init__(self,host,port):
self.host = host
self.port = port
self.tel = Telnet(host,port)
def command(self,s,annoyance=0):
try:
cmd = bytes(s,"utf-8")
self.tel.write(cmd+b"\n")
ans = self.tel.read_until(b"END")
ans = ans.decode('utf-8')
ans = re.sub("[\r\n]*END$","",ans)
ans = re.sub("^[\r\n]*","",ans)
# liquidsoap will time you out even if you're actively using the connection
# compensate by just reconnecting and doing it again since it's also stateless, lol
if ans == 'Connection timed out.. Bye!\r\n':
if annoyance > 6:
raise RuntimeError("BaseClient got annoyed, is the server down?")
self.tel.open(self.host,self.port)
return self.command(s,annoyance=annoyance+1)
else:
return ans
except OSError: # connection closed
if annoyance > 6:
raise RuntimeError("BaseClient got annoyed, is the server down?")
self.tel.open(self.host,self.port)
return self.command(s,annoyance=annoyance+1)
def parse_metadata(self,s):
def dohash(a):
h={}
a=list(a)
for i in range(int(len(a)/2)):
a[2*i+1] = re.sub('^"','',re.sub('"$','',a[2*i+1]))
if a[2*i] in ('2nd_queue_pos','rid','source_id'):
h[a[2*i]]=int(a[2*i+1])
else:
if a[2*i] in ('skip'):
if a[2*i+1]=='true':
h[a[2*i]]=True
else:
h[a[2*i]]=False
else:
h[a[2*i]]=a[2*i+1]
return h
def noblank(a):
return filter(lambda x: x!='',a)
# Avoid empty matched groups, otherwise there will be no element in
# the array. To do so, we include the ", and remove them later.
return [ dohash(noblank(re.compile('(.+)=(".*")\n').split(e))) for e in
noblank(re.compile('--- \d+ ---\n').split(self.command(s))) ]
class LiqClient(LiqBaseClient):
def __init__(self, host, port, main, request):
self.main = main
self.request = request
super().__init__(host, port)
def skip(self):
return (self.command(self.main+".skip") == "Done")
def queue(self, url):
item = self.command("{}.push {}".format(self.request, url))
return int(item)
def info(self, item=None):
if item:
return self.parse_metadata("{}.metadata {}".format(self.request, item))
else:
return self.parse_metadata("{}.metadata".format(self.main))
def np(self):
i = self.info()
i.reverse()
return [x for x in i if x['status'] == "playing"][0]
if __name__ == "__main__":
c = LiqClient('localhost',1234, "root","request")
print("Has been running for "+c.command("uptime"))
pprint.pprint(c.info())
import pdb; pdb.set_trace()
set("server.telnet",true)
set("server.telnet.bind_addr","127.0.0.1")
set("server.socket",true)
set("server.socket.path","/tmp/liquidsoap.sock")
normal = playlist(mode='randomize', reload=60, "./tracks/")
out = fallback([ request.queue(id="request", timeout=180.), normal ])
add_skip_command(out)
out = mksafe(out)
out = nrj(out)
ice = output.icecast(%vorbis, id="root",
host = "127.0.0.1", port = 8000,
password = "insert icecast password here lol", mount = "radio.ogg",
out)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment