diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4e5f6c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ \ No newline at end of file diff --git a/README.md b/README.md index 7a9824e..73b8f56 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,28 @@ -# MPD_Jabber_bot +# MPD Jabber bot +Jabber/XMPP bot that controls streaming MPD instance. + +This software is written in Ruby and allows you to control mpd server with simple commands written in MUC chat. + +It can skip to prev/next song, play/pause/stop playback, control outputs, return info about current song and song which will be played next. + +This software is still in alpha stage. It works for me so I can't promise updates in near future. Feel free to send me a patch if you want. I'll consider merging them. + +TODO: + +* Create persistent config file +* Code cleanup +* Possibly add some more features +* Make it work in all versions of private messages + +Do: + + <botnick>: help + +to get basic usage help. + +This bot depends on following ruby modules: + +* rubygems +* jabbot +* ruby-mpd \ No newline at end of file diff --git a/botik.rb b/botik.rb new file mode 100644 index 0000000..ea28cd5 --- /dev/null +++ b/botik.rb @@ -0,0 +1,294 @@ +#!/usr/bin/ruby +$VERBOSE = nil +$BOTVERSION = "beta 0.0.1" +scriptname = "botik.rb" # When its changed, bot will switch to dev mode + +botnick = "StreamCtl" + +require 'rubygems' +require 'jabbot' +require 'ruby-mpd' + +mpd = MPD.new 'localhost', 6600 +mpd.connect + +class MPD + def outputs + send_command 'outputs' + end + + def pause + send_command 'pause' + end + + def outputstatus(list) + outputs = outputrefresh() + if list.kind_of?(Array) then + conout = Hash.new + list.each { + |out| + conout[out.to_i] = outputs[out] + } + return conout + else + return outputs + end + end + + def outputtoggle(out) + out = out.to_i + outputs = outputrefresh() + if outputs[out].status == 0 then + send_command "enableoutput #{out}" + else + send_command "disableoutput #{out}" + end + end + + def outputrefresh + outarray = send_command 'outputs' + i = 0 + outputs = Hash.new + Struct.new("Output", :name, :status) + until(outarray.length < 1) + line = outarray[0] + outid = line[:outputid].to_i + outname = line[:outputname] + outstatus = (line[:outputenabled] ? 1 : 0) # Conversion to int + outputs[outid] = Struct::Output.new(outname, outstatus) + outarray.shift + end + return outputs + end + + def outputstatus(list) + outputs = outputrefresh() + if list.kind_of?(Array) then + conout = Hash.new + list.each { + |out| + conout[out.to_i] = outputs[out] + } + return conout + else + return outputs + end + end + + def outputtoggle(out) + out = out.to_i + outputs = outputrefresh() + if outputs[out].status == 0 then + send_command "enableoutput #{out}" + else + send_command "disableoutput #{out}" + end + end + def nextsong + stat = send_command "status" + nsid = stat[:nextsongid] + return send_command "playlistid #{nsid}" + end +end + +def mpdcontrol(mpd,request, mess, type) + arr = mess.text.split(/\W+/) + if arr[2].nil? == false then + if arr[2].chomp.strip != "" then + param = arr[2].chomp.strip + end + end + + def outputsctl (outputs,sitelink) + reply = "" + def site_state_translate (input) + if input == 0 then + return "off" + else + return "on" + end + end + outputs.each { + |key,var| + outfile = var[:name].split.last + weblink = "" + case outfile + when "HQ" + file = "hi.ogg" + when "LQ" + file = "lo.ogg" + when "MP3" + file = "stream.mp3" + end + reply = reply+"#{key} - #{sitelink}/#{file} - #{site_state_translate(var.status)}\n" + } + return reply + end + + status = mpd.status[:state].to_s.downcase + case request + when "status" + if status == "stop" then + sticon = "◼" + else + status == "play" ? sticon = "▶" : sticon = "▮▮" + end + song = mpd.current_song + if song.name != nil then + answer = "Stream: #{song.name} .:. #{song.title} @ #{song.file}" + else + answer = "#{song.artist} - #{song.title} [album #{song.album}]" + end + + song = mpd.current_song + if song.name != nil then +# do nothing + else + ns = mpd.nextsong + answer = "#{answer}\nNext song in queue: #{ns[:artist]} - #{ns[:title]} [album #{ns[:album]}]" + end + if type == "0" then + post "#{sticon} #{answer}" + else + post "#{sticon} #{answer}" => mess.user + end + when "play" + mpd.play + when "pause" + mpd.pause + when "stop" + mpd.stop + when "prev" + mpd.previous + when "next" + mpd.next + when "nextsong" + song = mpd.current_song + if song.name != nil then + answer = "MPD is playing external stream. I can't get next song info." + else + ns = mpd.nextsong + answer = "Next song in queue: #{ns[:artist]} - #{ns[:title]} [album #{ns[:album]}]" + end + if type == "0" then + post answer + else + post answer => mess.user + end + + when "toggle" + if param.nil? == false then + if param.to_i >= 0 and param.to_i <= 2 then + mpd.outputtoggle param.to_i + end + end + when "info" + if status == "stop" then + sticon = "◼" + else + status == "play" ? sticon = "▶" : sticon = "▮▮" + end + song = mpd.current_song + if song.name != nil then + answer = "Stream: #{song.name} .:. #{song.title} @ #{song.file}" + else + answer = "#{song.artist} - #{song.title} [album #{song.album}]" + end + if song.name != nil then + nsanswer = "MPD is playing external stream. I can't get next song info." + else + ns = mpd.nextsong + nsanswer = "Next song in queue: #{ns[:artist]} - #{ns[:title]} [album #{ns[:album]}]" + end + sitelink = "https://stream.example.com" + outputs = mpd.outputstatus([0, 1, 2]) + outs = outputsctl(outputs,sitelink) + + if type == "0" then + post "#{sticon} #{answer}\n#{nsanswer}\nOutputs:\n#{outs}" + else + post "#{sticon} #{answer}\n#{nsanswer}\nOutputs:\n#{outs}" => mess.user + end + when "outputs" + sitelink = "https://stream.example.com" + outputs = mpd.outputstatus([0, 1, 2]) + if type == "0" then + post outputsctl(outputs,sitelink) + else + post outputsctl(outputs,sitelink) => mess.user + end + when "version" + if type == "0" then + post "Pitriss MPD stream control bot, version #{$BOTVERSION}" + else + post "Pitriss MPD stream control bot, version #{$BOTVERSION}" => mess.user + end + when "help" + answer = "Stream is located at https://stream.example.com/xyz.ext (see outputs)\n +Commands:\n +status - Info about played track +nestsong - Info about next song in queue +info - Verbose info about most relevant aspects +outputs - list all outputs available and its status +toggle - toggle state of output +play - start MPD +pause - pause MPD +stop - stops MPD +next - next song +prev - previous song\n\n +For more info ask in room. (eg howto access playlist control)" + if type == "0" then + post answer + else + post answer => mess.user + end + else + if type == "0" then + post "Are you kiddin' me? That's complete bullshit! I don't believe single word you told me! Maybe you can try again with \"help\"?" + else + post "Are you kiddin' me? That's complete bullshit! I don't believe single word you told me! Maybe you can try again with \"help\"?" => mess.user + end + end +end + + +configure do |conf| + conf.login = "bot@example.com" + conf.password = "ChangeMe" +# conf.nick = "StreamCtl" + conf.nick = botnick + conf.resource = "StreamCtl" + if scriptname == File.basename($0) then + conf.channel = "some-room" + else + conf.channel = "botdev" + end + conf.server = "conf.example.com" +end + + +message(/^#{botnick}[,|:| ].*$/i) do |message, params| + arr = message.text.split(/\W+/) + if arr[1].nil? == false then + if arr[1].chomp.strip != "" then + mpdcontrol(mpd,arr[1].chomp.strip,message,"0") + else + mpdcontrol(mpd,"status",message,"0") + end + else + mpdcontrol(mpd,"status",message,"0") + end +end + +query do |message, params| + arr = message.text.split(/\W+/) + if arr[0].nil? == false then + if arr[0].chomp.strip != "" then + mpdcontrol(mpd,arr[0].chomp.strip,message, "1") + else + mpdcontrol(mpd,"status",message,"1") + end + else + mpdcontrol(mpd,"status",message,"1") + end +end