Updated README and CHANGELOG
This commit is contained in:
parent
c156d4051f
commit
49940cd08e
|
@ -1,6 +1,9 @@
|
|||
MumbleDJ Changelog
|
||||
==================
|
||||
|
||||
### November 15, 2014
|
||||
* Created "v2" branch for Ruby rewrite.
|
||||
|
||||
### November 9, 2014
|
||||
* Fixed volume changed message showing wrong value.
|
||||
|
||||
|
|
19
README.md
19
README.md
|
@ -1,23 +1,6 @@
|
|||
MumbleDJ
|
||||
========
|
||||
A Mumble bot that plays music fetched from YouTube videos. There are ways to play music with a bot on Mumble already, but I wasn't really satisfied with them. Many of them require the Windows client to be opened along with other applications which is not ideal. My goal with this project is to make a Linux-friendly Mumble bot that can run on a webserver instead of a personal computer.
|
||||
|
||||
## Setup
|
||||
Since the setup process is a bit extensive, the setup guide can be found in [SETUP.md](https://github.com/matthieugrieger/mumbledj/blob/master/SETUP.md).
|
||||
|
||||
## Dependencies
|
||||
* [OpenSSL](http://www.openssl.org/)
|
||||
* [Lua 5.2](http://www.lua.org/)
|
||||
* [libev](http://libev.schmorp.de/)
|
||||
* [protobuf-c](https://github.com/protobuf-c/protobuf-c)
|
||||
* [Ogg Vorbis](https://xiph.org/vorbis/)
|
||||
* [Opus](http://www.opus-codec.org/)
|
||||
* [Python 2.6 or above](https://www.python.org/)
|
||||
* [pafy](https://github.com/np1/pafy/)
|
||||
* [piepan](https://github.com/layeh/piepan)
|
||||
* [Jansson](http://www.digip.org/jansson/)
|
||||
* [jshon](http://kmkeen.com/jshon/)
|
||||
* [ffmpeg](https://www.ffmpeg.org/)
|
||||
A Mumble bot that plays music fetched from YouTube videos. I have decided to experiment with rewriting the bot in Ruby using [mumble-ruby](https://github.com/perrym5/mumble-ruby). I am hoping this will cut down on the dependency list, make it easier to develop in the future, and allow for some extra functionality that wasn't previously possible.
|
||||
|
||||
## Author
|
||||
[Matthieu Grieger](http://matthieugrieger.com)
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
-------------------------
|
||||
-- MumbleDJ --
|
||||
-- By Matthieu Grieger --
|
||||
-------------------------------------------------
|
||||
-- config.lua --
|
||||
-- This is where all the configuration options --
|
||||
-- for the bot can be set. --
|
||||
-------------------------------------------------
|
||||
|
||||
local config = {}
|
||||
-------------------------
|
||||
-- GENERAL CONFIGURATION
|
||||
-------------------------
|
||||
|
||||
-- Default channel
|
||||
-- DEFAULT VALUE: "Music"
|
||||
config.DEFAULT_CHANNEL = "Music"
|
||||
|
||||
-- Command prefix
|
||||
-- DEFAULT VALUE: "!"
|
||||
config.COMMAND_PREFIX = "!"
|
||||
|
||||
-- Show status output in console?
|
||||
-- DEFAULT VALUE: true
|
||||
config.OUTPUT = true
|
||||
|
||||
-- Default volume (1 being normal volume)
|
||||
-- DEFAULT VALUE: 0.5
|
||||
config.VOLUME = 0.5
|
||||
|
||||
-- Lowest volume allowed
|
||||
-- DEFAULT VALUE: 0.01
|
||||
config.LOWEST_VOLUME = 0.01
|
||||
|
||||
-- Highest volume allowed
|
||||
-- DEFAULT VALUE: 1.5
|
||||
config.HIGHEST_VOLUME = 1.5
|
||||
|
||||
-- Ratio that must be met or exceeded to trigger a song skip.
|
||||
-- DEFAULT VALUE: 0.5
|
||||
config.SKIP_RATIO = 0.5
|
||||
|
||||
|
||||
-------------------------
|
||||
-- COMMAND CONFIGURATION
|
||||
-------------------------
|
||||
|
||||
-- Alias used for add command.
|
||||
-- DEFAULT VALUE: "add"
|
||||
config.ADD_ALIAS = "add"
|
||||
|
||||
-- Alias used for skip command.
|
||||
-- DEFAULT VALUE: "skip"
|
||||
config.SKIP_ALIAS = "skip"
|
||||
|
||||
-- Alias used for volume command.
|
||||
-- DEFAULT VALUE: "volume"
|
||||
config.VOLUME_ALIAS = "volume"
|
||||
|
||||
-- Alias used for move command.
|
||||
-- DEFAULT VALUE: "move"
|
||||
config.MOVE_ALIAS = "move"
|
||||
|
||||
-- Alias used for kill command.
|
||||
-- DEFAULT VALUE: "kill"
|
||||
config.KILL_ALIAS = "kill"
|
||||
|
||||
|
||||
-----------------------
|
||||
-- ADMIN CONFIGURATION
|
||||
-----------------------
|
||||
|
||||
-- Enable admins (true = on, false = off)
|
||||
-- DEFAULT VALUE: true
|
||||
config.ENABLE_ADMINS = true
|
||||
|
||||
-- List of admins
|
||||
-- NOTE: I recommend only giving users admin privileges if they are registered
|
||||
-- on the server. Otherwise people can just take their username and issue admin
|
||||
-- commands.
|
||||
-- EXAMPLE:
|
||||
-- config.ADMINS = {"Matt", "Matthieu"}
|
||||
config.ADMINS = {"Matt", "Matthieu"}
|
||||
|
||||
-- Make add an admin command?
|
||||
-- DEFAULT VALUE: false
|
||||
config.ADMIN_ADD = false
|
||||
|
||||
-- Make skip an admin command?
|
||||
-- DEFAULT VALUE: false
|
||||
config.ADMIN_SKIP = false
|
||||
|
||||
-- Make volume an admin command?
|
||||
-- DEFAULT VALUE: true
|
||||
config.ADMIN_VOLUME = true
|
||||
|
||||
-- Make move an admin command?
|
||||
-- DEFAULT VALUE: true
|
||||
config.ADMIN_MOVE = true
|
||||
|
||||
-- Make kill an admin command?
|
||||
-- DEFAULT VALUE: true (I recommend never changing this to false)
|
||||
config.ADMIN_KILL = true
|
||||
|
||||
|
||||
----------------------
|
||||
-- CHAT CONFIGURATION
|
||||
----------------------
|
||||
|
||||
-- Enable/disable chat notifications for new songs (true = on, false = off)
|
||||
-- DEFAULT VALUE: true
|
||||
config.SHOW_NOTIFICATIONS = true
|
||||
|
||||
-------------------------
|
||||
-- MESSAGE CONFIGURATION
|
||||
-------------------------
|
||||
|
||||
-- Message shown to users when they do not have permission to execute a command.
|
||||
config.NO_PERMISSION_MSG = "You do not have permission to execute that command."
|
||||
|
||||
-- Message shown to users when they try to move the bot to a non-existant channel.
|
||||
config.CHANNEL_DOES_NOT_EXIST_MSG = "The channel you specified does not exist."
|
||||
|
||||
-- Message shown to users when they attempt to add an invalid URL to the queue.
|
||||
config.INVALID_URL_MSG = "The URL you submitted does not match the required format. Either you did not provide a YouTube URL, or an error occurred during the downloading & encoding process."
|
||||
|
||||
-- Message shown to users when they attempt to use the stop command when no music is playing.
|
||||
config.NO_MUSIC_PLAYING_MSG = "There is no music playing at the moment."
|
||||
|
||||
-- Message shown to users when they issue a command that requires an argument and one was not supplied.
|
||||
config.NO_ARGUMENT = "The command you issued requires an argument and you did not provide one. Make sure a space exists between the command and the argument."
|
||||
|
||||
-- Message shown to users when they try to change the volume to a value outside the volume range.
|
||||
config.NOT_IN_VOLUME_RANGE = "The volume you tried to supply is not in the allowed volume range. The value must be between " .. config.LOWEST_VOLUME .. " and " .. config.HIGHEST_VOLUME .. "."
|
||||
|
||||
-- Message shown to users when they successfully change the volume.
|
||||
config.VOLUME_SUCCESS = "You have successfully changed the volume to the following: %s."
|
||||
|
||||
|
||||
----------------------
|
||||
-- HTML CONFIGURATION
|
||||
----------------------
|
||||
|
||||
-- Message shown to channel when a new song starts playing.
|
||||
config.NOW_PLAYING_HTML = [[
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><img src="%s" width=150 /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b><a href="http://youtu.be/%s">%s</a> (%s)</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Added by %s</td>
|
||||
</tr>
|
||||
</table>
|
||||
]]
|
||||
|
||||
-- Message shown to channel when a song is added to the queue by a user.
|
||||
config.SONG_ADDED_HTML = [[
|
||||
<b>%s</b> has added "%s" to the queue.
|
||||
]]
|
||||
|
||||
-- Message shown to channel when a user votes to skip a song.
|
||||
config.USER_SKIP_HTML = [[
|
||||
<b>%s</b> has voted to skip this song.
|
||||
]]
|
||||
|
||||
-- Message shown to channel when a song has been skipped.
|
||||
config.SONG_SKIPPED_HTML = [[
|
||||
The number of votes required for a skip has been met. <b>Skipping song!</b>
|
||||
]]
|
||||
|
||||
return config
|
|
@ -1,156 +0,0 @@
|
|||
--Copyright (C) 2013-2014 by Pierre Chapuis
|
||||
--
|
||||
--Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
--of this software and associated documentation files (the "Software"), to deal
|
||||
--in the Software without restriction, including without limitation the rights
|
||||
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
--copies of the Software, and to permit persons to whom the Software is
|
||||
--furnished to do so, subject to the following conditions:
|
||||
--
|
||||
--The above copyright notice and this permission notice shall be included in
|
||||
--all copies or substantial portions of the Software.
|
||||
--
|
||||
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
--THE SOFTWARE.
|
||||
|
||||
local push_right = function(self, x)
|
||||
assert(x ~= nil)
|
||||
self.tail = self.tail + 1
|
||||
self[self.tail] = x
|
||||
end
|
||||
|
||||
local push_left = function(self, x)
|
||||
assert(x ~= nil)
|
||||
self[self.head] = x
|
||||
self.head = self.head - 1
|
||||
end
|
||||
|
||||
local peek_right = function(self)
|
||||
return self[self.tail]
|
||||
end
|
||||
|
||||
local peek_left = function(self)
|
||||
return self[self.head+1]
|
||||
end
|
||||
|
||||
local pop_right = function(self)
|
||||
if self:is_empty() then return nil end
|
||||
local r = self[self.tail]
|
||||
self[self.tail] = nil
|
||||
self.tail = self.tail - 1
|
||||
return r
|
||||
end
|
||||
|
||||
local pop_left = function(self)
|
||||
if self:is_empty() then return nil end
|
||||
local r = self[self.head+1]
|
||||
self.head = self.head + 1
|
||||
local r = self[self.head]
|
||||
self[self.head] = nil
|
||||
return r
|
||||
end
|
||||
|
||||
local rotate_right = function(self, n)
|
||||
n = n or 1
|
||||
if self:is_empty() then return nil end
|
||||
for i=1,n do self:push_left(self:pop_right()) end
|
||||
end
|
||||
|
||||
local rotate_left = function(self, n)
|
||||
n = n or 1
|
||||
if self:is_empty() then return nil end
|
||||
for i=1,n do self:push_right(self:pop_left()) end
|
||||
end
|
||||
|
||||
local _remove_at_internal = function(self, idx)
|
||||
for i=idx, self.tail do self[i] = self[i+1] end
|
||||
self.tail = self.tail - 1
|
||||
end
|
||||
|
||||
local remove_right = function(self, x)
|
||||
for i=self.tail,self.head+1,-1 do
|
||||
if self[i] == x then
|
||||
_remove_at_internal(self, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local remove_left = function(self, x)
|
||||
for i=self.head+1,self.tail do
|
||||
if self[i] == x then
|
||||
_remove_at_internal(self, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local length = function(self)
|
||||
return self.tail - self.head
|
||||
end
|
||||
|
||||
local is_empty = function(self)
|
||||
return self:length() == 0
|
||||
end
|
||||
|
||||
local contents = function(self)
|
||||
local r = {}
|
||||
for i=self.head+1,self.tail do
|
||||
r[i-self.head] = self[i]
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
local iter_right = function(self)
|
||||
local i = self.tail+1
|
||||
return function()
|
||||
if i > self.head+1 then
|
||||
i = i-1
|
||||
return self[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local iter_left = function(self)
|
||||
local i = self.head
|
||||
return function()
|
||||
if i < self.tail then
|
||||
i = i+1
|
||||
return self[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local methods = {
|
||||
push_right = push_right,
|
||||
push_left = push_left,
|
||||
peek_right = peek_right,
|
||||
peek_left = peek_left,
|
||||
pop_right = pop_right,
|
||||
pop_left = pop_left,
|
||||
rotate_right = rotate_right,
|
||||
rotate_left = rotate_left,
|
||||
remove_right = remove_right,
|
||||
remove_left = remove_left,
|
||||
iter_right = iter_right,
|
||||
iter_left = iter_left,
|
||||
length = length,
|
||||
is_empty = is_empty,
|
||||
contents = contents,
|
||||
}
|
||||
|
||||
local new = function()
|
||||
local r = {head = 0, tail = 0}
|
||||
return setmetatable(r, {__index = methods})
|
||||
end
|
||||
|
||||
return {
|
||||
new = new,
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#---------------------#
|
||||
# MumbleDJ #
|
||||
# By Matthieu Grieger #
|
||||
#---------------------#--------------------------------------------------#
|
||||
# download_audio.py #
|
||||
# Downloads audio (ogg format) from specified YouTube ID. If no ogg file #
|
||||
# exists, it creates an empty file called .video_fail that tells the Lua #
|
||||
# side of the program that the download failed. .video_fail will get #
|
||||
# deleted on the next successful download. #
|
||||
#------------------------------------------------------------------------#
|
||||
|
||||
import pafy
|
||||
from sys import argv
|
||||
from os.path import isfile
|
||||
from os import remove, system
|
||||
from time import sleep
|
||||
|
||||
url = argv[1]
|
||||
video = pafy.new(url)
|
||||
|
||||
try:
|
||||
video.oggstreams[0].download(filepath = 'song.ogg', quiet = True)
|
||||
if isfile('.video_fail'):
|
||||
remove('.video_fail')
|
||||
except:
|
||||
with open('.video_fail', 'w+') as f:
|
||||
f.close()
|
||||
|
||||
while isfile('song.ogg.temp'):
|
||||
sleep(1)
|
||||
|
||||
if isfile('song.ogg'):
|
||||
system('ffmpeg -i song.ogg -acodec libvorbis -ar 48000 -ac 1 -loglevel quiet song-converted.ogg -y')
|
||||
else:
|
||||
with open('.video_fail', 'w+') as f:
|
||||
f.close()
|
||||
|
||||
if not isfile('.video_fail'):
|
||||
while not isfile('song-converted.ogg'):
|
||||
sleep(1)
|
||||
|
||||
if isfile('song.ogg'):
|
||||
remove('song.ogg')
|
||||
|
|
@ -1,357 +0,0 @@
|
|||
-------------------------
|
||||
-- MumbleDJ --
|
||||
-- By Matthieu Grieger --
|
||||
------------------------------------------------------------------
|
||||
-- mumbledj.lua --
|
||||
-- The main file which defines most of MumbleDJ's behavior. All --
|
||||
-- commands are found here, and most of their implementation. --
|
||||
------------------------------------------------------------------
|
||||
|
||||
local config = require("config")
|
||||
local deque = require("deque")
|
||||
|
||||
-- Connects to Mumble server.
|
||||
function piepan.onConnect()
|
||||
print(piepan.me.name .. " has connected to the server!")
|
||||
local user = piepan.users[piepan.me.name]
|
||||
local channel = user.channel(config.DEFAULT_CHANNEL)
|
||||
if channel == nil then
|
||||
print("The channel '" .. config.DEFAULT_CHANNEL .. "' does not exist. Moving bot to root of server...")
|
||||
channel = piepan.channels[0]
|
||||
end
|
||||
piepan.me:moveTo(channel)
|
||||
end
|
||||
|
||||
-- Function that is called when a new message is posted to the channel.
|
||||
function piepan.onMessage(message)
|
||||
if message.user == nil then
|
||||
return
|
||||
end
|
||||
|
||||
if string.sub(message.text, 0, 1) == config.COMMAND_PREFIX then
|
||||
parse_command(message)
|
||||
end
|
||||
end
|
||||
|
||||
-- Parses commands and its arguments (if they exist), and calls the appropriate
|
||||
-- functions for doing the requested task.
|
||||
function parse_command(message)
|
||||
local command = ""
|
||||
local argument = ""
|
||||
if string.find(message.text, " ") then
|
||||
command = string.sub(message.text, 2, string.find(message.text, ' ') - 1)
|
||||
argument = string.sub(message.text, string.find(message.text, ' ') + 1)
|
||||
else
|
||||
command = string.sub(message.text, 2)
|
||||
end
|
||||
|
||||
-- Add command
|
||||
if command == config.ADD_ALIAS then
|
||||
local has_permission = check_permissions(config.ADMIN_ADD, message.user.name)
|
||||
|
||||
if has_permission then
|
||||
if config.OUTPUT then
|
||||
print(message.user.name .. " has added a song to the queue.")
|
||||
if not add_song(argument, message.user.name) then
|
||||
message.user:send(config.INVALID_URL_MSG)
|
||||
end
|
||||
end
|
||||
else
|
||||
message.user:send(config.NO_PERMISSION_MSG)
|
||||
end
|
||||
-- Skip command
|
||||
elseif command == config.SKIP_ALIAS then
|
||||
local has_permission = check_permissions(config.ADMIN_SKIP, message.user.name)
|
||||
|
||||
if has_permission then
|
||||
if config.OUTPUT then
|
||||
print(message.user.name .. " has voted to skip the current song.")
|
||||
end
|
||||
|
||||
skip(message.user.name)
|
||||
else
|
||||
message.user:send(config.NO_PERMISSION_MSG)
|
||||
end
|
||||
-- Volume command
|
||||
elseif command == config.VOLUME_ALIAS then
|
||||
local has_permission = check_permissions(config.ADMIN_VOLUME, message.user.name)
|
||||
|
||||
if has_permission then
|
||||
if config.OUTPUT then
|
||||
print(message.user.name .. " has changed the volume to the following: " .. argument .. ".")
|
||||
if argument ~= nil then
|
||||
if config.LOWEST_VOLUME <= tonumber(argument) and tonumber(argument) <= config.HIGHEST_VOLUME then
|
||||
config.VOLUME = tonumber(argument)
|
||||
message.user:send(string.format(config.VOLUME_SUCCESS, argument))
|
||||
else
|
||||
message.user:send(config.NOT_IN_VOLUME_RANGE)
|
||||
end
|
||||
else
|
||||
message.user:send(config.NO_ARGUMENT)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Move command
|
||||
elseif command == config.MOVE_ALIAS then
|
||||
local has_permission = check_permissions(config.ADMIN_MOVE, message.user.name)
|
||||
|
||||
if has_permission then
|
||||
if config.OUTPUT then
|
||||
print(message.user.name .. " has told the bot to move to the following channel: " .. argument .. ".")
|
||||
end
|
||||
if not move(argument) then
|
||||
message.user:send(config.CHANNEL_DOES_NOT_EXIST_MSG)
|
||||
end
|
||||
else
|
||||
message.user:send(config.NO_PERMISSION_MSG)
|
||||
end
|
||||
-- Kill command
|
||||
elseif command == config.KILL_ALIAS then
|
||||
local has_permission = check_permissions(config.ADMIN_KILL, message.user.name)
|
||||
|
||||
if has_permission then
|
||||
if config.OUTPUT then
|
||||
print(message.user.name .. " has told the bot to kill itself.")
|
||||
end
|
||||
kill()
|
||||
else
|
||||
message.user:send(config.NO_PERMISSION_MSG)
|
||||
end
|
||||
else
|
||||
message.user:send("The command you have entered is not valid.")
|
||||
end
|
||||
end
|
||||
|
||||
-- Handles a skip request through the use of helper functions found within
|
||||
-- song_queue.lua. Once done processing, it will compare the skip ratio with
|
||||
-- the one defined in the settings and decide whether to skip the current song
|
||||
-- or not.
|
||||
function skip(username)
|
||||
if add_skip(username) then
|
||||
local skip_ratio = count_skippers() / count_users()
|
||||
piepan.me.channel:send(string.format(config.USER_SKIP_HTML, username))
|
||||
if skip_ratio > config.SKIP_RATIO then
|
||||
piepan.me.channel:send(config.SONG_SKIPPED_HTML)
|
||||
piepan.Audio:stop()
|
||||
next_song()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Moves the bot to the channel specified by the "chan" argument.
|
||||
-- NOTE: This only supports moving to a sibling channel at the moment.
|
||||
function move(chan)
|
||||
local user = piepan.users[piepan.me.name]
|
||||
local channel = user.channel("../" .. chan)
|
||||
if channel == nil then
|
||||
return false
|
||||
else
|
||||
piepan.me:moveTo(channel)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Performs functions that allow the bot to safely exit.
|
||||
function kill()
|
||||
os.remove("song.ogg")
|
||||
os.remove("song-converted.ogg")
|
||||
os.remove(".video_fail")
|
||||
piepan.disconnect()
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
-- Checks the permissions of a user against the config to see if they are
|
||||
-- allowed to execute a certain command.
|
||||
function check_permissions(ADMIN_COMMAND, username)
|
||||
if config.ENABLE_ADMINS and ADMIN_COMMAND then
|
||||
return is_admin(username)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Checks if a user is an admin, as specified in config.lua.
|
||||
function is_admin(username)
|
||||
for _,user in pairs(config.ADMINS) do
|
||||
if user == username then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Switches to the next song.
|
||||
function next_song()
|
||||
reset_skips()
|
||||
if get_length() ~= 0 then
|
||||
local success = get_next_song()
|
||||
if not success then
|
||||
piepan.me.channel:send("An error occurred while preparing the next track. Skipping...")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Checks if a file exists.
|
||||
function file_exists(file)
|
||||
local f=io.open(file,"r")
|
||||
if f~=nil then io.close(f) return true else return false end
|
||||
end
|
||||
|
||||
-- Returns the number of users in the Mumble server.
|
||||
function count_users()
|
||||
local user_count = -1 -- Set to -1 to account for the bot
|
||||
for name,_ in pairs(piepan.users) do
|
||||
user_count = user_count + 1
|
||||
end
|
||||
return user_count
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Song Queue Stuff --
|
||||
-- Contains the definition of the song queue --
|
||||
-- used for queueing up songs. --
|
||||
-------------------------------------------------
|
||||
|
||||
local song_queue = deque.new()
|
||||
local skippers = {}
|
||||
|
||||
-- Begins the process of adding a new song to the song queue.
|
||||
function add_song(url, username)
|
||||
local patterns = {
|
||||
"https?://www%.youtube%.com/watch%?v=([%d%a_%-]+)",
|
||||
"https?://youtube%.com/watch%?v=([%d%a_%-]+)",
|
||||
"https?://youtu.be/([%d%a_%-]+)",
|
||||
"https?://youtube.com/v/([%d%a_%-]+)",
|
||||
"https?://www.youtube.com/v/([%d%a_%-]+)"
|
||||
}
|
||||
|
||||
for _,pattern in ipairs(patterns) do
|
||||
local video_id = string.match(url, pattern)
|
||||
if video_id ~= nil and string.len(video_id) < 20 then
|
||||
return get_youtube_info(video_id, username)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Retrieves the metadata for the specified YouTube video via the gdata API.
|
||||
function get_youtube_info(id, username)
|
||||
if id == nil then
|
||||
return false
|
||||
end
|
||||
local cmd = [[
|
||||
wget -q -O - 'http://gdata.youtube.com/feeds/api/videos/%s?v=2&alt=jsonc' |
|
||||
jshon -Q -e data -e title -u -p -e duration -u -p -e thumbnail -e hqDefault -u
|
||||
]]
|
||||
local jshon = io.popen(string.format(cmd, id))
|
||||
local name = jshon:read()
|
||||
local duration = jshon:read()
|
||||
local thumbnail = jshon:read()
|
||||
if name == nil or duration == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return youtube_info_completed({
|
||||
id = id,
|
||||
title = name,
|
||||
duration = string.format("%d:%02d", duration / 60, duration % 60),
|
||||
thumbnail = thumbnail,
|
||||
username = username
|
||||
})
|
||||
end
|
||||
|
||||
-- Notifies the channel that a song has been added to the queue, and plays the
|
||||
-- song if it is the first one in the queue.
|
||||
function youtube_info_completed(info)
|
||||
if info == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
song_queue:push_right(info)
|
||||
|
||||
local message = string.format(config.SONG_ADDED_HTML, info.username, info.title)
|
||||
piepan.me.channel:send(message)
|
||||
|
||||
if not piepan.Audio.isPlaying() then
|
||||
return get_next_song()
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Deletes the old song and begins the process of retrieving a new one.
|
||||
function get_next_song()
|
||||
reset_skips()
|
||||
if file_exists("song-converted.ogg") then
|
||||
os.remove("song-converted.ogg")
|
||||
end
|
||||
if song_queue:length() ~= 0 then
|
||||
local next_song = song_queue:pop_left()
|
||||
return start_song(next_song)
|
||||
end
|
||||
end
|
||||
|
||||
-- Downloads/encodes the audio file and then begins to play it.
|
||||
function start_song(info)
|
||||
os.execute("python download_audio.py " .. info.id)
|
||||
if not file_exists(".video_fail") then
|
||||
while not file_exists("song-converted.ogg") do
|
||||
os.execute("sleep " .. tonumber(2))
|
||||
end
|
||||
if piepan.Audio:isPlaying() then
|
||||
piepan.Audio:stop()
|
||||
end
|
||||
piepan.me.channel:play({filename="song-converted.ogg", volume=config.VOLUME}, get_next_song)
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
if piepan.Audio:isPlaying() then
|
||||
local message = string.format(config.NOW_PLAYING_HTML, info.thumbnail, info.id, info.title, info.duration, info.username)
|
||||
piepan.me.channel:send(message)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Adds the username of a user who requested a skip. If their name is
|
||||
-- already in the list nothing will happen.
|
||||
function add_skip(username)
|
||||
local already_skipped = false
|
||||
for _,name in pairs(skippers) do
|
||||
if name == username then
|
||||
already_skipped = true
|
||||
end
|
||||
end
|
||||
if not already_skipped then
|
||||
table.insert(skippers, username)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Counts the number of users who would like to skip the current song and
|
||||
-- returns it.
|
||||
function count_skippers()
|
||||
local skipper_count = 0
|
||||
for name,_ in pairs(skippers) do
|
||||
skipper_count = skipper_count + 1
|
||||
end
|
||||
|
||||
return skipper_count
|
||||
end
|
||||
|
||||
-- Resets the list of users who would like to skip a song. Called during a
|
||||
-- transition between songs.
|
||||
function reset_skips()
|
||||
skippers = {}
|
||||
end
|
||||
|
||||
-- Retrieves the length of the song queue and returns it.
|
||||
function get_length()
|
||||
return song_queue:length()
|
||||
end
|
Reference in a new issue