summaryrefslogtreecommitdiff
path: root/crystal/irc.cr
diff options
context:
space:
mode:
Diffstat (limited to 'crystal/irc.cr')
-rwxr-xr-xcrystal/irc.cr950
1 files changed, 0 insertions, 950 deletions
diff --git a/crystal/irc.cr b/crystal/irc.cr
deleted file mode 100755
index 3b592e9..0000000
--- a/crystal/irc.cr
+++ /dev/null
@@ -1,950 +0,0 @@
-require "twitch/irc"
-require "http"
-require "uri"
-require "twitcr"
-require "json"
-require "crystal_mpd"
-
-STDOUT.sync = true
-
-struct Nil
- def as_s?
- self
- end
-end
-
-settings = Hash(String, String).new
-
-settings["configdir"] = Path.home./("/.config/bungmobott/").to_s
-settings["statedir"] = Path.home./("/.local/state/bungmobott/").to_s
-
-if ENV["LOCALAPPDATA"]?
- settings["configdir"] = Path.windows( ENV["LOCALAPPDATA"] + "\\bungmobott\\" ).to_s
- settings["statedir"] = Path.windows( ENV["LOCALAPPDATA"] + "\\bungmobott\\state\\" ).to_s
-end
-
-settings["tempdir"] = "/tmp/bungmobott/"
-ENV["TEMP"]? && ( settings["tempdir"] = "#{ENV["TEMP"]}\\bungmobott\\" )
-
-ENV["XDG_CONFIG_HOME"]? && ( settings["configdir"] = ENV["XDG_CONFIG_HOME"] + "/bungmobott/" )
-#ENV["XDG_DATA_HOME"]? && ( settings["datadir"] = ENV["XDG_DATA_HOME"] ) # unused?
-ENV["XDG_STATE_HOME"]? && ( settings["statedir"] = ENV["XDG_STATE_HOME"] + "/bungmobott/" )
-
-Dir.mkdir_p( settings["configdir"] )
-Dir.mkdir_p( settings["statedir"] )
-Dir.mkdir_p( settings["tempdir"] )
-
-settings["home"] = Path.home.to_s
-regextwitchuser = /^[0-9a-zA-Z_]+$/
-
-error = false
-[ "access_token", "client_id" ].each do |key|
- begin
- settings[key] = File.read( settings["configdir"] + key ).chomp
- rescue IO::Error
- STDERR.puts "ERROR: Missing " + settings["configdir"] + key
- error = true
- end
-end
-
-# enable gcloud?
-if File.exists?( settings["configdir"] + "gcloud_token" ) && ( settings["gcloud_token"] = File.read( settings["configdir"] + "gcloud_token" ).chomp )
- gcloud = true
-else
- gcloud = false
- STDERR.puts "Warning: #{settings["configdir"]}gcloud_token is missing; GCS voices disabled."
-end
-
-# enable aws?
-if ! File.exists?( ENV["USERPROFILE"] + "\\.aws\\credentials" )
- STDERR.puts "Warning: #{ENV["USERPROFILE"]}\\.aws\\credentials is missing; AWS voices disabled."
- aws = false
-elsif ! Process.find_executable( "aws.exe" )
- STDERR.puts "Warning: aws.exe is missing; AWS voices disabled."
- aws = false
-else
- aws = true
-end
-
-client = Twitcr::Client.new( settings )
-
-# derive channel_id from channel or vice versa
-if (
- File.exists?( settings["configdir"] + "channel" ) &&
- ( settings["channel"] = File.read( settings["configdir"] + "channel" ).chomp )
- ) || (
- File.exists?( settings["configdir"] + "channel_id" ) &&
- ( settings["channel_id"] = File.read( settings["configdir"] + "channel_id" ).chomp )
- )
- if ! ( settings["channel"]? =~ regextwitchuser ) && settings["channel_id"]? =~ /^[0-9]+$/
- settings["channel"] = client.user( settings["channel_id"].to_u64 ).login
- elsif ( settings["channel_id"]? =~ regextwitchuser ) && ! settings["channel_id"]? =~ /^[0-9]+$/
- settings["channel_id"] = client.user( settings["channel"] ).id.to_s
- end
-else
- STDERR.puts "ERROR: Missing #{settings["configdir"]}channel and channel_id configuration keys."
- error = true
- # exit 2
-end
-if error == true
- {% if flag?(:windows) %}
- puts "press enter to end program"
- gets
- {% end %}
- exit 1
-end
-
-if File.exists?( settings["configdir"] + "chatuser" )
- File.read( settings["configdir"] + "chat_user" ).chomp
-else
- STDERR.puts "Missing " + settings["configdir"] + "chat_user; using configured channel instead."
- settings["chat_user"] = settings["channel"]
-end
-
-obsipc = Channel( String ).new
-ircipc = Channel( Tuple( String, String ) ).new
-t2sipc = Channel( Tuple( String, String ) ).new
-
-snifflast = Int64.new(0)
-
-def urbandef( term : String )
- #http://api.urbandictionary.com/v0/define?term=waifu
- client = HTTP::Client.new( "api.urbandictionary.com" )
- response = client.exec( "GET", "/v0/define?term=" + URI.encode_www_form( term ) )
- puts response.status_code
- json = JSON.parse( response.body )
- return json["list"][0]["definition"].to_s.gsub( /[\[\]]/, "" ).gsub( /\n/, "" )
-end
-
-# Currently only used in flags?(:unix)
-def t2smsg( settings : Hash(String, String), msg : String)
- if File.exists?( settings["statedir"] + "/.t2s.sock" )
- sock = Socket.unix
- sock.connect Socket::UNIXAddress.new( settings["statedir"] + "/.t2s.sock" )
- sock.puts( msg )
- sock.close
- end
-rescue ex
- puts ex
-end
-
-def effectsmsg( settings : Hash(String, String), msg : String )
- if File.exists?( settings["statedir"] + "/.effects.sock" )
- sock = Socket.unix
- sock.connect Socket::UNIXAddress.new( settings["statedir"] + "/.effects.sock" )
- sock.puts( msg )
- sock.close
- end
-rescue ex
- puts ex
-end
-
-def userlog( ircipc : Channel, settings : Hash(String, String), message : FastIRC::Message )
- unless ( ( prefix = message.prefix ) && ( chatuser = prefix.source ) && ( uid = message.tags["user-id"]? ) )
- return nil
- end
- basedir = settings["statedir"]
- userdir = basedir + "/uids/" + uid
- Dir.mkdir_p( basedir + "/names" )
- Dir.mkdir_p( userdir + "/names" )
- unless testrefuser2uid( userdir + "/names/" + chatuser )
- namelatest = ""
- datelatest = Time::UNIX_EPOCH
- Dir.each_child( userdir + "/names/" ) do |name|
- namedate = File.info( userdir + "/names/" + name ).modification_time
- if namedate > datelatest
- namelatest = name
- datelatest = namedate
- end
- end
- unless namelatest.empty?
- ircipc.send( { "##{settings["channel"]}", "Rename detected: #{uid}: #{namelatest} -> #{chatuser}" } )
- end
- genrefuser2uid( userdir + "/names/" + chatuser, uid, 3 )
- end
- unless testrefuser2uid( basedir + "/names/" + chatuser )
- genrefuser2uid( basedir + "/names/" + chatuser, uid, 1 )
- end
- unless ( message.params[0] == "##{settings["channel"]}" )
- return nil
- end
- return ( [ chatuser, uid, userdir ] )
-rescue ex
- puts ex
-end
-
-def getvoice( settings : Hash(String, String), userdir : String, chatuser : String )
- if File.exists?( userdir + "/voice" )
- voice_output = File.read( userdir + "/voice" ).chomp
- voice_setting = voice_output
- else
- voice_output = File.read( settings["configdir"] + "/voicelist.txt" ).chomp.split( "\n" ).sample( 1 )[0].chomp
- voice_setting = "random"
- end
- if File.exists?( userdir + "/voicesub" )
- namesub = File.read( userdir + "/voicesub" ).chomp
- else
- namesub = chatuser
- end
- return( [namesub, voice_setting, voice_output ] )
-end
-
-# text2speech
-def t2s( t2sipc : Channel, settings : Hash(String, String), userdir : String, chatuser : String, text : String )
- if ( text !~ /^ *(!|\|)/ )
- namesub, voice_setting, voice = getvoice( settings, userdir, chatuser )
- subs = Array( Tuple( Regex, String ) ){
- { /http(s|):\/\/([a-z0-9.-]+)\/[a-zA-Z0-9\/&=%-_]+/, "link to \\2" },
- { /([^a-zA-Z0-9])-/, "\\1 dash "},
- { /\|/, " vertical bar "},
- { /\`/, " grave accent "},
- { /\+/, " plus "},
- { /×/, " multiplied by "},
- { /=/, " equals "},
- { /\//, " slash "},
- { /\\/, " backslash "},
- { /@/, " at "},
- { /&/, " and "},
- { />/, " greater than "},
- { /</, " less than "},
- { /_/, " underscore "},
- { /\.\.\./, " dot dot dot "},
- { /\^/, " circumflex accent "},
- { /\#/, " octothorpe "},
- { /:([^ ])/, " colon \\1"},
- { /;([^ ])/, " semicolon \\1"},
- { /\.([^ ])/, " dot \\1"},
- { /\%([^ ])/, " percent sign \\1"},
- { /!([^ ])/, " tchik \\1"},
- { /([^ ])\$/, "\\1 dollar sign "},
- { /0/, " zero " },
- { /1/, " one " },
- { /2/, " two " },
- { /3/, " three " },
- { /4/, " four " },
- { /5/, " five " },
- { /6/, " six " },
- { /7/, " seven " },
- { /8/, " eight " },
- { /9/, " nine " },
- { /rrr.+/, "rr" },
- }.each do | subtuple |
- text = text.gsub( subtuple[0], subtuple[1] )
- end
- {% if flag?(:windows) %} # send to thread
- t2sipc.send( { voice, "#{namesub} #{text}" } )
- {% elsif flag?(:unix) %} # send to socket
- t2smsg( settings, "#{voice} #{namesub} #{text}" )
- {% end %}
- return( voice )
- else
- return( nil )
- end
-end
-
-def reversesource( scenes : Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ), currentscene : String )
-# "meta-cam-front" => {
-# "vidcap-c920" => {
-# "render" => true, "type" => "dshow_input", "x" => 0.0, "y" => 0.0
-# },
-# "gst-bungfront" => {
-# "type" => "gstreamer-source", "x" => 0.0, "y" => 0.0
-# }
-# }
-# # scenes[currentscene][source]["type"] => "scene"
- sourcemap = Hash( String, String ).new
- scenes[currentscene].each do | source, sourcedata |
- # maybe make this recurse later
- sourcemap[source.to_s] = currentscene
- if sourcedata["type"] == "scene"
- scenes[source.to_s].each do | metasource, metasourcedata |
- sourcemap[metasource.to_s] = source.to_s
- end
- end
- end
- return sourcemap
-end
-
-def playmp3file( filepath : String )
- p = Process.new(
- "powershell.exe",
- [ "-Command", "#Set-PSDebug -Trace 1;
- Add-Type -AssemblyName presentationCore;
- $player = New-Object system.windows.media.mediaplayer;
- $player.open(\"#{filepath}\");
- $player.volume = .99;
- $player.play();
- Start-Sleep -Milliseconds 1000;
- $duration = $player.NaturalDuration.TimeSpan.TotalMilliseconds;
- Start-Sleep -Milliseconds ($duration - 1000 );
- "],
- output: STDOUT, error: STDERR
- )
- # https://geekeefy.wordpress.com/2016/07/19/powershellmediaplayer/ has some ideas
- p.wait
-end
-
-macro testrefuser2uid( path )
- {% if flag?(:windows) %}
- File.exists?( {{path}} ) && ( File.read( {{ path }} ) =~ /^[0-9]+$/ )
- {% elsif flag?(:unix) %}
- File.symlink?( {{path}} )
- {% end %}
-end
-
-macro genrefuser2uid( path, uid, depth )
- {% if flag?(:windows) %}
- File.write( {{path}}, {{uid}}.to_s )
- {% elsif flag?(:unix) %}
- pp "unix"
- File.symlink( "../"*{{depth}} + "uids/#{{{uid}}}", {{path}} )
- {% end %}
-end
-
-macro temporaryduplicate( item )
- request = Hash( String, String | Bool ){
- "request-type" => "SetSceneItemProperties",
- "message-id" => "SetSceneItemProperties",
- "scene-name" => "meta",
- "item" => {{item}},
- "visible" => true,
- }
- obsipc.send( request.to_json )
- request = Hash( String, String | Hash( String, String ) ){
- "request-type" => "DuplicateSceneItem",
- "message-id" => "DuplicateSceneItem",
- "item" => { "name" => {{item}} },
- "fromScene" => "meta"
- }
- ircipc.send( { "##{settings["channel"]}", "Duplicating source #{match[2]}" } )
- obsipc.send( request.to_json )
-end
-
-voices = Hash(String, String).new
-File.each_line( settings["configdir"] + "/voicelist.txt" ) do |line|
- voices[ line.downcase ] = line
-end
-
-lastvoice = Array(String).new
-
-# Put tts stuff into the same thread so each playback blocks the next
-spawn do
- loop do
- begin
- while t2stuple = t2sipc.receive
- voice, text = [ *t2stuple ]
- if ( match = voice.match( /^Microsoft-([A-Za-z]+)/ ) )
- if ( match[1] =~ /Lili|Mary|Mike|Sam|Anna/ )
- msttsvoice="Microsoft #{match[1]}"
- else
- msttsvoice="Microsoft #{match[1]} Desktop"
- end
- p = Process.new(
- "powershell.exe",
- [ "-Command", "
- Add-Type -AssemblyName System.Speech;
- $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;
- $speak.SelectVoice(\"#{msttsvoice}\");
- $speak.Speak($Input);
- $speak.Finalize;
- "],
- input: Process::Redirect::Pipe, output: STDOUT
- )
- p.input.puts text
- p.input.close
- p.wait
- elsif gcloud && ( match = voice.match( /^([a-zA-Z]{2,3}-[a-zA-Z]{2})/ ) ) # Google cloud voice
- request = Hash( String, Hash( String, String ) ){
- "input" => { "text" => text },
- "audioConfig" => { "audioEncoding" => "MP3" },
- "voice" => {
- "name" => voice,
- "languageCode" => match[1],
- },
- }
- body = request.to_json
- ssl_context = OpenSSL::SSL::Context::Client.new
- ssl_context.verify_mode = OpenSSL::SSL::VerifyMode::NONE
-
- headers = HTTP::Headers.new
- headers["Content-Type"] = "application/json; charset=utf-8"
-
- response = HTTP::Client.exec( "POST", "https://texttospeech.googleapis.com/v1/text:synthesize?key=#{settings["gcloud_token"]}", headers, body, tls: ssl_context )
-
- response.body
-
- filepath="#{settings["tempdir"]}#{Time.utc.to_unix}.mp3"
- json=JSON.parse(response.body)
- File.write( filepath, Base64.decode_string( json["audioContent"].as_s ) )
- playmp3file( filepath )
- File.delete( filepath )
- elsif aws # AWS polly voices
- filepath="#{settings["tempdir"]}#{Time.utc.to_unix}.mp3"
- p = Process.new(
- "aws.exe", [
- "polly", "synthesize-speech",
- "--output-format", "mp3",
- "--voice-id", voice,
- "--text", text,
- filepath
- ], output: STDOUT, error: STDERR
- )
- p.wait
- playmp3file( filepath )
- File.delete( filepath )
- else # unknown
- STDERR.puts "Voice not recognized or available."
- end
- end
- rescue ex
- puts ex
- end
- end
-end
-
-# obs-websocket thread
-spawn do
- loop do
- begin
-
- obs_pubsub = HTTP::WebSocket.new( URI.parse( "ws://127.0.0.1:4444/" ), HTTP::Headers{"Cookie" => "SESSIONID=1234"} )
- # Outgoing
- # state
- currentscene = "unknown"
- # scenename => { sourcename => { property => value }
- scenes = Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ).new
- spawn do
- while msg = obsipc.receive
- pp msg
- if ( match = msg.match( /^\x10source-toggle ([a-z0-9-_]+)/ ) )
- source = match[1]
- sources = reversesource( scenes, currentscene )
- pp sources
- request = Hash( String, String | Bool ).new
- request["request-type"] = "SetSceneItemProperties"
- request["message-id"] = "SetSceneItemProperties"
- #request["request-type"] = "GetSceneItemProperties"
- request["message-id"] = "toggle-source"
- scene = sources[source]
- request["scene-name"] = sources[source]
- request["item"] = source
- if scenes[scene][source]["render"] == true
- request["visible"] = false
- else
- request["visible"] = true
- end
- obs_pubsub.send( request.to_json )
- elsif ( match = msg.match( /^\x10source ([a-z0-9-_]+)/ ) )
- # FIXME
- else
- obs_pubsub.send( msg )
- end
- end
- end
- # Incoming
- obs_pubsub.on_message do | message |
- json = JSON.parse( message )
- if json["error"]?
- puts json["error"]
- ircipc.send( { "#" + settings["channel"], "| obs: #{json["error"]}" } )
- next
- end
- case json["update-type"]?
- when "StreamStatus" # these are a bit noisy,
- next # so skip them
- when "SwitchScenes"
- ircipc.send( { "#" + settings["channel"], "| obs: switched scene to " + ( json["scene-name"]?.as_s? || "unknown" ) } )
- currentscene = json["scene-name"].as_s
- puts "currentscene is " + currentscene
- when "MediaEnded"
- request = Hash( String, String | Bool ){
- "request-type" => "SetSceneItemProperties",
- "message-id" => "SetSceneItemProperties",
- "item" => json["sourceName"].as_s,
- "visible" => false
- }
- ircipc.send( { "##{settings["channel"]}", "Disabling #{json["sourceName"].as_s}" } )
- obsipc.send( request.to_json )
- #"sourceName": "media-youdied"
- puts "currentscene is " + currentscene
- if ( ( json["sourceName"].as_s =~ /^media-/ ) && ( currentscene != "meta" ) )
- request = Hash( String, String | Hash( String, String ) ){
- "request-type" => "DeleteSceneItem",
- "message-id" => "DeleteSceneItem",
- "item" => { "name" => json["sourceName"].as_s },
- }
- ircipc.send( { "##{settings["channel"]}", "Deleting #{json["sourceName"].as_s}" } )
- obsipc.send( request.to_json )
- end
- when "SourceFilterVisibilityChanged"
- ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["sourceName"]} filter #{json["filterName"]} visibility is now #{json["filterEnabled"]}" } )
- when "SceneItemVisibilityChanged"
- ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["item-name"]} visibility is now #{json["item-visible"].as_bool}" } )
- end
- print( "RECEIVED: ")
- print( json.to_pretty_json )
- print( "\n")
- case json["message-id"]?
- when "GetCurrentScene"
- currentscene = json["name"].as_s
- ircipc.send( { "#" + settings["channel"], "| obs: " + json["sources"].as_a.map{ |source| source["name"] }.join(", ") } )
- when "GetSourceFilters"
- ircipc.send( { "#" + settings["channel"], "| obs: " + json["filters"].as_a.map{ |filter| filter["name"] }.join(", ") } )
- when "GetSceneList"
- ircipc.send( { "#" + settings["channel"], "| obs: " + json["scenes"].as_a.map{ |scene| scene["name"] }.join(", ") } )
- json["scenes"].as_a.each{ |scene|
- scenes[scene["name"].as_s]? || ( scenes[scene["name"].as_s] = Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ).new )
- scene["sources"].as_a.each{ |source|
- scenes[scene["name"].as_s][source["name"]]? || ( scenes[scene["name"].as_s][source["name"].as_s] = Hash( String, Int64 | Float64 | Bool | String ).new )
- source["render"].as_bool != Nil && ( scenes[scene["name"].as_s][source["name"].as_s]["render"] = source["render"].as_bool )
- source["type"].as_s? && ( scenes[scene["name"].as_s][source["name"].as_s]["type"] = source["type"].as_s )
- source["x"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["x"] = source["x"].as_f )
- source["y"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["y"] = source["y"].as_f )
- source["cx"].as_i64? && ( scenes[scene["name"].as_s][source["name"].as_s]["cx"] = source["cx"].as_i64 )
- source["cy"].as_i64? && ( scenes[scene["name"].as_s][source["name"].as_s]["cy"] = source["cy"].as_i64 )
- source["source_cx"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["source_cx"] = source["source_cx"].as_f )
- source["source_cy"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["source_cy"] = source["source_cy"].as_f )
- }
- }
- pp scenes
- when "toggle-filter"
- name = json["filters"][0]["name"].as_s
- visible = ( json["filters"][0]["enabled"].as_bool == false )
- ircipc.send( { "#" + settings["channel"], "| obs: Setting visibility of filter #{name} to #{visible}" } )
- obsipc.send( "{ \"request-type\": \"SetSourceFilterVisibility\", \"message-id\": \"SetSourceFilterVisibility\", \"sourceName\": \"#{name}\", \"visible\": #{visible} }" )
- #This is a dumb hack to toggle SetSceneItemProperty visibility
- when "toggle-source"
- name = json["name"].as_s
- visible = ( json["visible"].as_bool == false )
- ircipc.send( { "#" + settings["channel"], "| obs: Setting visibility of source #{name} to #{visible}" } )
- obsipc.send( "{ \"request-type\": \"SetSceneItemProperties\", \"message-id\": \"SetSceneItemProperties\", \"item\": \"#{name}\", \"visible\": #{visible} }" )
- end
- end
- request = Hash( String, String ){
- "request-type" => "GetCurrentScene",
- "message-id" => "GetCurrentScene",
- }
- obsipc.send( request.to_json )
-
- obs_pubsub.run
- rescue ex : Socket::ConnectError
- # these are a bit noisy
- #pp ex
- obs_pubsub && obs_pubsub.close
- rescue ex : IO::Error
- pp ex
- ircipc.send( { "#" + settings["channel"], "obs.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') + ": Maybe try again in 10 seconds?" } )
- rescue ex
- pp ex
- pp ex.backtrace?
- ircipc.send( { "#" + settings["channel"], "irc.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') } )
- obs_pubsub && obs_pubsub.close
- end
- sleep 10
- next
- end
-end
-
-# main thread: IRC
-loop do
- begin
-
- bot = Twitch::IRC::Client.new( nick: settings["chat_user"], token: "oauth:" + settings["access_token"], log_mode: true )
- bot.tags = [ "membership", "tags", "commands" ]
-
- # Outgoing IRC message thread
- # "Most IRC servers limit messages to 512 bytes in length, including the trailing CR-LF characters."
- # PRIVMSG #channel message\r\n
- spawn do
- while msgtuple = ircipc.receive # why does this need to be a tuple?
- sizelimit=( 512 - ( msgtuple[0].size + 11 ) )
- bot.message( msgtuple[0], msgtuple[1][0..sizelimit] ) # limit size
- end
- end
-
- # Create a handler to process incoming messages
- bot.on_message do |message|
- spawn do
- next unless ( userlogreturn = userlog( ircipc, settings, message ) )
- chatuser, uid, userdir = userlogreturn
- if ( chatuser == "pipne" ) && ( ( Time.utc.to_unix - snifflast ) >= 14400 )
- snifflast = Time.utc.to_unix
- t2smsg( settings, chatuser + " sniff null" )
- end
- if ( t2sreturn = t2s( t2sipc, settings, userdir, chatuser, message.params[1] ) )
- lastvoice.insert( 0, t2sreturn )
- lastvoice = lastvoice[0..4]
- end
- next unless ( ( match = message.params[1].match(/^ *!([A-Za-z]+) (([a-zA-Z0-9= _\:,.&'\/?;\\\(\)\[\]+\-]|!)+)/) || message.params[1].match(/^ *!([A-Za-z]+)/) ) )
- cmd = match[1]
- own = ( message.tags["room-id"] == message.tags["user-id"] )
- vip = ( message.tags["badges"].to_s.matches?( /(^|,)vip\// ) )
- mod = ( message.tags["mod"] == "1" )
- sub = ( message.tags["subscriber"] == "1" )
- if ( ( cmd =~ /^(substitute|voicesub)$/ ) && ( mod || sub ) )
- case message.params[1].split( " " ).size
- when 1
- if File.exists?( userdir + "/voicesub" )
- bot.message( "##{settings["channel"]}", "| Current name substitution is \"#{File.read( userdir + "/voicesub" )}\"." )
- else
- bot.message( "##{settings["channel"]}", "| Current name substitution is disabled." )
- end
- else
- if match[2]?
- voicesub = match[2].downcase
- if voicesub =~ /^(disabled|null|disable|none)$/
- if File.exists?( userdir + "/voicesub" )
- File.delete( userdir + "/voicesub" )
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now disabled." )
- else
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is already disabled." )
- end
- else
- Dir.mkdir_p( userdir )
- File.write( userdir + "/voicesub", voicesub )
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" )}\"." )
- end
- end
- end
- elsif ( cmd =~ /^(commands|help)$/ )
- bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/txt/commands.txt" )
- elsif ( cmd =~ /^(dexem)$/ )
- ircipc.send( { "##{settings["channel"]}", "| You're doing great work, Dexem!" } )
- elsif ( ( cmd == "lastvoice" ) && ( mod || sub ) )
- unless lastvoice.empty?
- bot.message( "##{settings["channel"]}", "| Last voices were " + lastvoice.join( ", " ) )
- else
- bot.message( "##{settings["channel"]}", "| No voices used so far." )
- end
- elsif ( ( cmd =~ /^(voices|voicelist)$/ ) && ( mod || sub ) )
- bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/voicelist.txt" )
- elsif ( ( cmd =~ /^(setvoice|voice)$/ ) && ( mod || sub ) )
- case message.params[1].split( " " ).size
- when 1
- namesub, voice_setting, voice_output = getvoice( settings, userdir, chatuser )
- bot.message( "##{settings["channel"]}", "| Current voice is #{voice_setting}" )
- when 2
- if match[2]?
- voice = match[2].downcase
- if voice =~ /disabled|null|disable|none|random/
- if File.exists?( userdir + "/voice" )
- File.delete( userdir + "/voice" )
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now random." )
- else
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is already random." )
- end
- elsif voices.has_key?( voice )
- csvoice = voices[voice]
- Dir.mkdir_p( userdir )
- File.write( userdir + "/voice", csvoice )
- pp userdir
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" )}." )
- # TODO: make separate script to print streamelements URLs to client machine
- else
- pp ( match )
- bot.message( "##{settings["channel"]}", "| Invalid voice. To see list, use !voices" )
- end
- end
- when 3
- else
- end
- elsif ( ( cmd =~ /^scene$/ ) && ( mod || own ) )
- if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- obsipc.send( "{ \"request-type\": \"SetCurrentScene\", \"message-id\": \"SetCurrentScene\", \"scene-name\": \"" + match[2] + "\" }" )
- else
- obsipc.send( "{ \"request-type\": \"GetSceneList\", \"message-id\": \"GetSceneList\" }" )
- end
- elsif ( ( cmd =~ /^source$/ ) && ( mod || own ) )
- request = Hash( String, String | Bool ).new
- if ( match[2]? ) && ( sourceargs = match[2].match( /^([a-zA-Z0-9-_]+) +(true|false)/ ) )
-# request["request-type"] = "SetSceneItemProperties"
-# request["message-id"] = "SetSceneItemProperties"
-# request["item"] = sourceargs[1]
-# sourceargs[2] == "true" && ( request["visible"] = true )
-# sourceargs[2] == "false" && ( request["visible"] = false )
-#
-# ircipc.send( { "##{settings["channel"]}", "Setting source #{sourceargs[1]} visibility to #{sourceargs[2]}" } )
- obsipc.send( "\x10source #{match[2]}" )
- elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- #sources = reversesource( scenes, currentscene )
- #request["request-type"] = "SetSceneItemProperties"
- #request["message-id"] = "SetSceneItemProperties"
- #request["request-type"] = "GetSceneItemProperties"
- #request["message-id"] = "toggle-source"
- #request["item"] = match[2]
- #obsipc.send( request.to_json )
- obsipc.send( "\x10source-toggle #{match[2]}" )
- else
-# request["request-type"] = "GetCurrentScene"
-# request["message-id"] = "GetCurrentScene"
-# obsipc.send( request.to_json )
- obsipc.send( "\x10source #{match[2]}" )
- end
- elsif ( ( cmd =~ /^filter$/ ) && ( mod || own ) )
- request = Hash( String, String | Bool ){
- "request-type" => "GetSourceFilters"
- }
- if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+) (true|false)/ ) )
- request["request-type"] = "SetSourceFilterVisibility"
- request["message-id"] = "SetSourceFilterVisibility"
- request["sourceName"] = filterargs[1]
- request["filterName"] = filterargs[2]
- #request["filterEnabled"] = filterargs[3]
- filterargs[3] == "true" && ( request["filterEnabled"] = true )
- filterargs[3] == "false" && ( request["filterEnabled"] = false )
- obsipc.send( request.to_json )
- elsif ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+)/ ) )
- request["message-id"] = "toggle-filter"
- request["sourceName"] = filterargs[1]
- request["filterName"] = filterargs[2]
- obsipc.send( request.to_json )
- elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- request["sourceName"] = match[2]
- request["message-id"] = "GetSourceFilters"
- obsipc.send( request.to_json )
- else
- ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument, and optionally one filter name." } )
- end
- elsif ( cmd == "duplicate" && ( mod || own || vip ) )
- if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+)/ ) )
- temporaryduplicate( match[2] )
- else
- ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } )
- end
- elsif ( cmd =~ /^(metaminute|youdied)$/ )
- request = Hash( String, String | Bool ).new
- request["request-type"] = "SetSceneItemProperties"
- request["message-id"] = "SetSceneItemProperties"
- request["item"] = "media-" + cmd
- # This breaks playback (???)
- #request["visible"] = false
- #obsipc.send( request.to_json )
- #sleep 0.5
- # I should probably set this up to add the source, and then delete the source at the end of playback.
- request["visible"] = true
- #ircipc.send( { "##{settings["channel"]}", "Playing media-#{cmd}" } )
- obsipc.send( request.to_json )
- elsif ( ( cmd =~ /^(status|title)$/ ) && ( mod || own ) )
- begin
- if match[2]?
- client.put_channel!( settings["channel_id"].to_u64, title: match[2] )
- json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
- ircipc.send( { "##{settings["channel"]}", "Title is now \"#{ json["data"][0]["title"] }\""} )
- else
- json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
- ircipc.send( { "##{settings["channel"]}", "| Title is currently \"#{ json["data"][0]["title"] }\""} )
- end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "| An error occurred! " + ex.message.to_s } )
- end
- elsif ( ( cmd =~ /^(game|category)$/ ) && ( mod || own ) )
- begin
- if match[2]?
- puts "2 matches"
- client.put_channel!( settings["channel_id"].to_u64, game: match[2] )
- json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
- ircipc.send( { "##{settings["channel"]}", "Game is now \"#{ json["data"][0]["game_name"] }\""} )
- else
- puts "1 matches"
- json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
- ircipc.send( { "##{settings["channel"]}", "| Game is currently \"#{ json["data"][0]["game_name"] }\""} )
- end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "| An error occurred! " + ex.message.to_s } )
- end
- elsif ( ( cmd == "urban" ) && ( mod || own || sub || vip ) )
- if match[2]? && match[2] =~ /^([a-zA-Z0-9 -])+$/
- definition = urbandef( match[2] )
- ircipc.send( { "##{settings["channel"]}", definition[0,400] } )
- else
- ircipc.send( { "##{settings["channel"]}", "| Urban Dictionary search term should consist of letters, numbers, spaces, and/or hyphens." } )
- end
- elsif ( cmd == "matrix" )
- effectsmsg( settings, "overlay glmatrix" )
- elsif ( cmd =~ /juggle|juggler/ )
- effectsmsg( settings, "overlay juggler3d" )
- elsif ( cmd =~ /fireworks|firework/ )
- effectsmsg( settings, "overlay fireworkx" )
- elsif ( cmd == "pipes" )
- if match[2]? && match[2] =~ /^(fast|faster)$/
- effectsmsg( settings, "overlay pipes " + match[2] )
- else
- effectsmsg( settings, "overlay pipes" )
- end
- elsif ( cmd == "jellyfish" )
- effectsmsg( settings, "overlay hydrostat" )
- elsif ( cmd == "gluten" )
- effectsmsg( settings, "overlay flyingtoasters" )
- elsif ( cmd =~ /^(glsnake|glmatrix|gibson|xmatrix|flyingtoasters|moebiusgears|fireworkx|hydrostat|hypertorus|jigsaw|juggler3d|kaleidocycle|kumppa|molecule|noof|polyhedra)$/ )
- effectsmsg( settings, "overlay " + cmd )
- elsif ( cmd =~ /gltext|cowsay|xcowsay|cowfuscious/ )
- ( cmd == "cowfuscious" ) && ( cmd = "xcowsay" )
- if ( match[2]? ) && ( gltextargs = match[2].match( /^([0-9]+) +(.+)$/ ) )
- seconds=UInt64.new( 1 )
- seconds=gltextargs[1].to_u64
- if ( own || mod || vip )
- effectsmsg( settings, "overlay #{cmd} #{match[2]}" )
- puts "matched #{cmd} #{match[2]}"
- elsif ( sub )
- if ( seconds > 20 )
- seconds=20
- end
- effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
- puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
- else
- if ( seconds > 5 )
- seconds=5
- end
- effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
- puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
- end
- elsif match[2]? && match[2] =~ /^.+$/
- effectsmsg( settings, "overlay #{cmd} #{match[2]}" )
- puts "matched #{cmd} #{match[2]}"
- else
- effectsmsg( settings, "overlay #{cmd}" )
- puts "failed to match gltext"
- end
- elsif ( cmd =~ /^(cow(s|)|cowabunga|holycow$)/ )
- if match[2]? && match[2] =~ /^([0-9])+$/
- effectsmsg( settings, "overlay bouncingcow #{match[2]}" )
- else
- effectsmsg( settings, "overlay bouncingcow" )
- end
- elsif ( cmd == "overlay" )
- if match[2]? && match[2] =~ /^[a-z]+$/
- ircipc.send( { "##{settings["channel"]}", "| overlay requires an argument consisting wholly of lower case characters."} )
- effectsmsg( settings, "overlay #{match[2]}" )
- else
- end
- {% if flag?(:unix) %}
- elsif ( cmd =~ /^(system|dxdiag|computer)$/ )
- ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/tmp/DxDiag.txt" } )
- {% end %}
- elsif ( cmd == "hackerman" )
- ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/img/hackerman.jpg" } )
- elsif ( cmd =~ /^(songrequest|sr)$/ ) && match[2]?
- puts ("song detected: #{match[2]}")
- if ( ( match[2] =~ /list=/ ) && ( match[2] !~ /v=/ ) )
- ircipc.send( { "##{settings["channel"]}", "| Lists are not accepted.\n" } )
- elsif Process.run( "sraddsong.sh", {match[2]} )
- m = MPD::Client.new
- currentsong = m.currentsong
- if ( currentsong ) && ( currentsong["file"].to_s == "http://music/music.ogg" )
- m.next
- end
- if ( status = m.status ) && ( playlistinfo = m.playlistinfo )
- ircipc.send( { "##{settings["channel"]}", "| " + playlistinfo[ status["playlistlength"].to_i - 1 ]["file"].to_s + " added in position " + status["playlistlength"].to_s } )
- else
- ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
- end
- m.disconnect
- else
- end
- elsif ( cmd =~ /^current(|song)$/ )
- m = MPD::Client.new
- if ( currentsong = m.currentsong )
- ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
- else
- ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
- end
- m.disconnect
- elsif ( cmd =~ /^seek$/ )
- if ( ( match[2] ) && ( match[2] =~ /([+-]|)[0-9]/ ) )
- m = MPD::Client.new
- m.seekcur( match[2] )
- if ( ( musicstatus = m.status ) && ( pos = musicstatus["elapsed"].to_f.to_u64 ) )
- min=( pos / 60 ).to_u64
- sec=( pos % 60 ).to_u64
- ircipc.send( { "##{settings["channel"]}", "| " + sprintf( "2%d:2%d", min, sec ) } )
- else
- ircipc.send( { "##{settings["channel"]}", "| An error occurred. " } )
- end
- m.disconnect
- else
- ircipc.send( { "##{settings["channel"]}", "| Seek requires an argument of an absolute position in integer number of seconds, or a relative position in in signed integer number of seconds such as +60" } )
- end
- elsif ( cmd =~ /^next(|song)$/ )
- m = MPD::Client.new
- m.next
- if ( status = m.status ) && ( status["playlistlength"].to_i > 0 )
- if ( currentsong = m.nextsong )
- ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
- else
- ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
- end
- else
- ircipc.send( { "##{settings["channel"]}", "| Playlist is now empty." } )
- end
- m.disconnect
- elsif ( cmd == "followage" )
- begin
- if match[2]?
- args = match[2].split(/\s/)
- if args[1]?
- json = JSON.parse( client.get_user_follows( from: client.user_id( args[0] ).to_u64 , to: client.user_id( args[1] ).to_u64 ) )
- puts client.user_id( args[0] ).to_s
- puts client.user_id( args[1] ).to_s
- puts json
- ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } )
- elsif args[0]?
- json = JSON.parse( client.get_user_follows( from: client.user_id( args[0] ).to_u64 , to: settings["channel_id"].to_u64 ) )
- puts client.user_id( args[0] ).to_s
- puts json
- ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } )
- end
- else
- json = JSON.parse( client.get_user_follows( from: uid.to_u64 , to: settings["channel_id"].to_u64 ) )
- puts json
- ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } )
- end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "An error occurred! " + ex.message.to_s } )
- end
- end
- end
- end
-
- rooms = Array( String ).new
- rooms = [ "##{settings["channel"]}" ]
-
- # Connect to Twitch
- bot.run( rooms.map{ | room | room.sub( /^#/, "") } )
- rescue ex : IO::Error
- pp ex
- sleep 1
- # loop to reconnect
- rescue ex
- pp ex
- {% if flag?(:windows) %}
- puts "press enter to end program"
- gets
- {% end %}
- exit 1
- end
-end
-
-
-# FastIRC::Message.to_s
-# @badge-info=;badges=;color=;display-name=BungMonkey;emotes=;flags=;id=20fcc358-4fc3-4919-8229-f1034743d18f;mod=0;room-id=46694819;subscriber=0;tmi-sent-ts=1587876383907;turbo=0;user-id=59895482;user-type= :bungmonkey!bungmonkey@bungmonkey.tmi.twitch.tv PRIVMSG #kr3wzz test
-
-# FastIRC::Message.tags
-# {"badge-info" => "subscriber/34",
-# "badges" => "broadcaster/1,subscriber/12",
-# "color" => "",
-# "display-name" => "BungMonkey",
-# "emote-only" => "1",
-# "emotes" => "300780134:0-9",
-# "flags" => "",
-# "id" => "c5c08c05-6e39-483f-b426-488dfc477a6c",
-# "mod" => "0",
-# "room-id" => "59895482",
-# "subscriber" => "1",
-# "tmi-sent-ts" => "1587871386678",
-# "turbo" => "0",
-# "user-id" => "59895482",
-# "user-type" => ""}
-
-#command = "PRIVMSG"
-#prefix = Prefix(@source="bungmonkey", @user="bungmonkey", @host="bungmonkey.tmi.twitch.tv")
-#params = ["#bungmonkey", "test test test test"]