diff --git a/CHANGELOG.md b/CHANGELOG.md
index debef08..736d777 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
MumbleDJ Changelog
==================
+### December 8, 2014
+* Switched from Ruby to Go, using `gumble` instead of `mumble-ruby` now.
+
### November 15, 2014
* Created "v2" branch for Ruby rewrite.
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index 85d477f..0000000
--- a/Gemfile
+++ /dev/null
@@ -1,6 +0,0 @@
-# MumbleDJ Gemfile
-source "https://rubygems.org"
-
-gem "mumble-ruby"
-gem "spotify"
-gem "mkfifo"
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 6dfefe2..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,42 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- activesupport (4.1.8)
- i18n (~> 0.6, >= 0.6.9)
- json (~> 1.7, >= 1.7.7)
- minitest (~> 5.1)
- thread_safe (~> 0.1)
- tzinfo (~> 1.1)
- ffi (1.9.6)
- hashie (3.3.1)
- i18n (0.6.11)
- json (1.8.1)
- libspotify (12.1.51.4)
- minitest (5.4.3)
- mkfifo (0.0.1)
- mumble-ruby (1.1.2)
- activesupport
- hashie
- opus-ruby
- ruby_protobuf
- wavefile
- opus-ruby (1.0.1)
- ffi
- performer (1.0.1)
- ruby_protobuf (0.4.11)
- spotify (12.6.0)
- ffi (~> 1.0, >= 1.0.11)
- libspotify (~> 12.1.51)
- performer (~> 1.0)
- thread_safe (0.3.4)
- tzinfo (1.2.2)
- thread_safe (~> 0.1)
- wavefile (0.6.0)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- mkfifo
- mumble-ruby
- spotify
diff --git a/README.md b/README.md
index a95eb63..bfccd56 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
MumbleDJ
========
-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.
+A Mumble bot that plays music fetched from YouTube videos. I have decided to experiment with rewriting the bot in Go using [gumble](https://github.com/layeh/gumble). 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.
+
+And yes, I know that technically this is v3. The Ruby implementation had problems with high CPU usage and choppy audio which I couldn't seem to figure out.
## Author
[Matthieu Grieger](http://matthieugrieger.com)
@@ -32,6 +34,5 @@ THE SOFTWARE.
## Thanks
* All those who contribute to [Mumble](https://github.com/mumble-voip/mumble).
-* [perrym5](https://github.com/perrym5) for [mumble-ruby](https://github.com/perrym5/mumble-ruby).
-* [Kim Burgestrand](https://github.com/Burgestrand) for [libspotify Ruby bindings](https://github.com/Burgestrand/spotify).
+* [Tim Cooper](https://github.com/bontibon) for [gumble](https://github.com/layeh/gumble).
* [Ricardo Garcia](https://github.com/rg3) for [youtube-dl](https://github.com/rg3/youtube-dl).
diff --git a/mumbledj/.gitignore b/mumbledj/.gitignore
deleted file mode 100644
index b229014..0000000
--- a/mumbledj/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-certs
diff --git a/mumbledj/config.rb b/mumbledj/config.rb
deleted file mode 100644
index 10a4327..0000000
--- a/mumbledj/config.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-# MumbleDJ
-# By Matthieu Grieger
-# config.rb
-
-
-# ------------------------
-# CONNECTION CONFIGURATION
-# ------------------------
-
-# Bot username
-# DEFAULT VALUE: "MumbleDJ"
-BOT_USERNAME = "MumbleDJTest"
-
-# Password to join Mumble server
-# DEFAULT VALUE: "" (leave it as this value if no password is required)
-MUMBLE_PASSWORD = ENV['MUMBLE_PW']
-
-# Server address
-# DEFAULT VALUE: "localhost"
-MUMBLE_SERVER_ADDRESS = "matthieugrieger.com"
-
-# Server port number
-# DEFAULT VALUE: 64738
-MUMBLE_SERVER_PORT = 64738
-
-
-# ---------------------
-# GENERAL CONFIGURATION
-# ---------------------
-
-# Default channel
-# DEFAULT VALUE: "Music"
-DEFAULT_CHANNEL = "Bot Testing"
-
-# Command prefix
-# DEFAULT VALUE: "!"
-COMMAND_PREFIX = "!"
-
-# Show status output in console?
-# DEFAULT VALUE: true
-OUTPUT_ENABLED = true
-
-# Default volume
-# DEFAULT VALUE: 0.2
-VOLUME = 0.2
-
-# Lowest volume allowed
-# DEFAULT VALUE: 0.01
-LOWEST_VOLUME = 0.01
-
-# Highest volume allowed
-# DEFAULT VALUE: 0.6
-HIGHEST_VOLUME = 0.6
-
-# Ratio that must be met or exceeded to trigger a song skip
-# DEFAULT VALUE: 0.5
-SKIP_RATIO = 0.5
-
-
-# ---------------------
-# COMMAND CONFIGURATION
-# ---------------------
-
-# Alias used for add command
-# DEFAULT VALUE: "add"
-ADD_ALIAS = "add"
-
-# Alias used for skip command
-# DEFAULT VALUE: "skip"
-SKIP_ALIAS = "skip"
-
-# Alias used for volume command
-# DEFAULT VALUE: "volume"
-VOLUME_ALIAS = "volume"
-
-# Alias used for move command
-# DEFAULT VALUE: "move"
-MOVE_ALIAS = "move"
-
-# Alias used for mute command
-# DEFAULT VALUE: "mute"
-MUTE_ALIAS = "mute"
-
-# Alias used for unmute command
-# DEFAULT VALUE: "unmute"
-UNMUTE_ALIAS = "unmute"
-
-
-# -------------------
-# ADMIN CONFIGURATION
-# -------------------
-
-# Enable admins (true = on, false = off)
-# DEFAULT VALUE: true
-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.
-ADMINS = ["DrumZ"]
-
-# Make add an admin command?
-# DEFAULT VALUE: false
-ADMIN_ADD = false
-
-# Make skip an admin command?
-# DEFAULT VALUE: false
-ADMIN_SKIP = false
-
-# Make volume an admin command?
-# DEFAULT VALUE: true
-ADMIN_VOLUME = true
-
-# Make move an admin command?
-# DEFAULT VALUE: true
-ADMIN_MOVE = true
-
-# Make mute an admin command?
-# DEFAULT VALUE: true
-ADMIN_MUTE = true
-
-# Make unmute an admin command?
-# DEFAULT VALUE: true
-ADMIN_UNMUTE = true
-
-
-#----------------------
-# MESSAGE CONFIGURATION
-#----------------------
-
-# Message shown to users when they do not have permission to execute a command.
-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.
-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.
-INVALID_URL_MSG = "The URL you submitted does not match the required format."
-
-# Message shown to users when they attempt to use the stop command when no music is playing.
-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.
-NO_ARGUMENT_MSG = "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.
-NOT_IN_VOLUME_RANGE_MSG = "The volume you tried to supply is not in the allowed volume range. The value must be between #{LOWEST_VOLUME} and #{HIGHEST_VOLUME}."
-
-# Message shown to users when they successfully change the volume.
-VOLUME_SUCCESS_MSG = "You have successfully changed the volume to the following: %s."
-
-# Message shown to users when they try to skip a song they have already skipped.
-ALREADY_SKIPPED_MSG = "You have already voted to skip this song."
-
-# Message shown to users when the required number of votes to trigger a skip has been met.
-SKIP_SUCCESS_MSG = "Number of required skip votes has been met. Skipping song!"
diff --git a/mumbledj/mumbledj.rb b/mumbledj/mumbledj.rb
deleted file mode 100644
index 129c6f4..0000000
--- a/mumbledj/mumbledj.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-# MumbleDJ v2
-# By Matthieu Grieger
-# mumbledj.rb
-
-require "mumble-ruby"
-require "mkfifo"
-require_relative "config"
-require_relative "song_queue"
-
-# Class that defines MumbleDJ behavior.
-class MumbleDJ
-
- attr_reader :username, :server_address, :server_port, :default_channel
-
- # Initializes a new instance of MumbleDJ. The parameters are as follows:
- # username: Desired username of the Mumble bot
- # server_address: IP address/web address of Mumble server to connect to
- # server_port: Port number of Mumble server (generally 64738)
- # default_channel: The channel you would like the bot to connect to by
- # default. If the channel does not exist, the bot will connect to
- # the root channel of the server instead.
- # password: Password to join a password-protected server
- def initialize(username, server_address, server_port, default_channel, password)
- @username = username
- @password = password
- @server_address = server_address
- @server_port = server_port
- @default_channel = default_channel
- @song_queue = SongQueue.new
-
- Mumble.configure do |conf|
- conf.sample_rate = 48000
- conf.bitrate = 32000
- conf.ssl_cert_opts[:cert_dir] = File.expand_path("certs")
- end
- end
-
- # Connects to the Mumble server with the credentials specified in
- # initialize.
- def connect
- @client = Mumble::Client.new(@server_address) do |conf|
- conf.username = @username
- if @password != ""
- conf.password = @password
- end
- end
-
- set_callbacks
-
- @client.connect
- @client.on_connected do
- if @default_channel != ""
- @client.join_channel(@default_channel)
- end
- end
- end
-
- # Safely disconnects the bot from the server.
- def disconnect
- @client.disconnect
- end
-
- private
-
- # Parses messages looking for commands, and calls the appropriate
- # methods to complete each requested command.
- def parse_message(message)
- @sender = @client.users[message.actor].name
- if message.message[0] == COMMAND_PREFIX
- if message.message.count(" ") != 0
- @command = message.message[1..(message.message.index(" ") - 1)]
- @argument = message.message[(message.message.index(" ") + 1)..-1]
- else
- @command = message.message[1..-1]
- end
-
- case @command
- when ADD_ALIAS
- if has_permission?(ADMIN_ADD, @sender)
- add(@sender, @argument)
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when SKIP_ALIAS
- if has_permission?(ADMIN_SKIP, @sender)
- skip(@sender)
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when VOLUME_ALIAS
- if has_permission?(ADMIN_VOLUME, @sender)
- volume(@sender, @argument)
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when MOVE_ALIAS
- if has_permission?(ADMIN_MOVE, @sender)
- move(@sender, @argument)
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when MUTE_ALIAS
- if has_permission?(ADMIN_MUTE, @sender)
- @client.me.mute
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when UNMUTE_ALIAS
- if has_permission?(ADMIN_UNMUTE, @sender)
- @client.me.mute(false)
- else
- @client.text_user(@sender, NO_PERMISSION_MSG)
- end
- when 'test'
- File.mkfifo('/tmp/audio_stream.fifo')
- `youtube-dl --output audio --write-info-json --quiet --format bestaudio https://www.youtube.com/watch?v=5xfEr2Oxdys`
- spawn 'ffmpeg -y -i audio -f s16le -acodec pcm_s16le -ar 24000 -loglevel quiet /tmp/audio_stream.fifo'
- @client.player.stream_named_pipe('/tmp/audio_stream.fifo')
- else
- @client.text_user(@sender, INVALID_COMMAND_MSG)
- end
- end
- end
-
- # Sets various callbacks that can be triggered during the connection.
- def set_callbacks
- @client.on_text_message do |message|
- parse_message(message)
- end
- end
-
- # Checks message sender against ADMINS array to verify if they have
- # permission to use a specific command.
- def has_permission?(admin_command, sender)
- if ENABLE_ADMINS and admin_command
- return ADMINS.include?(sender)
- else
- return true
- end
- end
-
- def add(sender, url)
- if OUTPUT_ENABLED
- puts("#{sender} has added a song to the queue.")
- end
- if @song_queue.add_song?(url, sender)
- @client.text_channel(@client.me.current_channel.name, "#{sender} has added a song to the queue.")
- else
- @client.text_user(sender, INVALID_URL_MSG)
- end
- end
-
- def skip(sender)
- if OUTPUT_ENABLED
- puts("#{sender} has voted to skip the current song.")
- end
- if @song_queue.get_current_song.add_skip?(sender)
- @client.text_channel(@client.me.current_channel.name, "#{sender} has voted to skip the current song.")
- if @song_queue.get_current_song.skip_now?(@client.me.current_channel.users.count - 1)
- @client.text_channel(@client.me.current_channel.name, SKIP_SUCCESS_MSG)
- @song_queue.get_current_song.skip
- end
- else
- @client.text_user(sender, ALREADY_SKIPPED_MSG)
- end
- end
-
- def volume(sender, vol)
-
- end
-
- def move(sender, channel)
- begin
- @client.join_channel(channel)
- rescue Mumble::ChannelNotFound
- @client.text_user(sender, CHANNEL_DOES_NOT_EXIST_MSG)
- end
- end
-end
diff --git a/mumbledj/run_bot.rb b/mumbledj/run_bot.rb
deleted file mode 100644
index 0d85cd8..0000000
--- a/mumbledj/run_bot.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# MumbleDJ
-# By Matthieu Grieger
-# run_bot.rb
-
-require_relative "mumbledj"
-require_relative "config"
-require "thread"
-
-bot = MumbleDJ.new(username=BOT_USERNAME, server_address=MUMBLE_SERVER_ADDRESS, port=MUMBLE_SERVER_PORT,
- default_channel=DEFAULT_CHANNEL, password=MUMBLE_PASSWORD)
-bot.connect
-
-begin
- t = Thread.new do
- $stdin.gets
- end
-
- t.join
- rescue Interrupt => e
-end
diff --git a/mumbledj/song.rb b/mumbledj/song.rb
deleted file mode 100644
index eca2a52..0000000
--- a/mumbledj/song.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# MumbleDJ v2
-# By Matthieu Grieger
-# song.rb
-
-require_relative "config"
-
-# Base Song class that defines default behavior for any kind of song.
-class Song
-
- # Starts the song.
- def start
-
- end
-
- # Gets the name of the user who submitted the song.
- def get_submitter
- return @submitter
- end
-
- # Adds a skipper to the skips array for the current song.
- def add_skip?(username)
- if not @skips.include?(username)
- @skips << username
- return true
- else
- return false
- end
- end
-
- # Determines if a skip should occur. Returns true if a skip is needed,
- # false otherwise.
- def skip_now?(total_users)
- return (total_users / @skips.count) >= SKIP_RATIO
- end
-end
-
-class YouTubeSong < Song
-
- attr_reader :url, :submitter, :song_title, :song_duration, :song_thumbnail_url
-
- # Initializes the YouTubeSong object and retrieves the song title,
- # duration, and thumbnail URL from the YouTube API.
- def initialize(url, submitter)
- @url = url
- @submitter = submitter
- @skips = []
- # TODO: Retrieve YouTube information
- @song_title = ""
- @song_duration = ""
- @song_thumbnail_url = ""
- end
-
- # Downloads the audio for the YouTube video and returns the filename.
- def download_audio
-
- end
-end
diff --git a/mumbledj/song_queue.rb b/mumbledj/song_queue.rb
deleted file mode 100644
index c7975ee..0000000
--- a/mumbledj/song_queue.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# MumbleDJ v2
-# By Matthieu Grieger
-# song_queue.rb
-
-require_relative "song"
-
-# A specialized SongQueue class that handles queueing/unqueueing songs
-# and other actions.
-class SongQueue
-
- attr_reader :queue
-
- # Initializes a new song queue.
- def initialize
- @queue = []
- end
-
- # Checks if song already exists in the queue, and adds it if it doesn't
- # already exist.
- def add_song?(url, submitter)
- youtube_regex = /(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_\-]+))/x
-
- if youtube_regex.match(url)
- audio_type = "youtube"
- end
-
- if @queue.empty?
- if audio_type == "youtube"
- song = YouTubeSong.new(url, submitter)
- end
- @queue.push(song)
- else
- @queue.each do |song|
- if song.url == url
- return false
- end
- end
- if audio_type == "youtube"
- song = YouTubeSong.new(url, submitter)
- end
- @queue.push(song)
- end
- end
-
- # Processes a song delete request. Searches the queue for songs with
- # titles containing the keyword. If found, the song is deleted if the
- # username of the user who requested the deletion matches the
- # username of who originally added the song.
- def delete_song?(keyword, username)
- if not @queue.empty?
- @queue.each do |song|
- if song.song_title.includes?(keyword)
- if song.get_submitter == username
- @queue.delete(song)
- return true
- end
- end
- end
- return false
- else
- return false
- end
-
- end
-
- # Returns a formatted string that contains information about the next
- # song in the queue.
- def peek_next
-
- end
-
- # Returns the current Song object from the queue.
- def get_current_song
- return @queue[0]
- end
-end