diff options
-rw-r--r-- | crystal/lib/bungmobott/src/bungmobott.cr | 13 | ||||
-rw-r--r-- | crystal/tcpsocket.cr | 408 |
2 files changed, 214 insertions, 207 deletions
diff --git a/crystal/lib/bungmobott/src/bungmobott.cr b/crystal/lib/bungmobott/src/bungmobott.cr index 4fdb9d2..3df034f 100644 --- a/crystal/lib/bungmobott/src/bungmobott.cr +++ b/crystal/lib/bungmobott/src/bungmobott.cr @@ -13,11 +13,15 @@ #chat_user: # twitch: bungmonkey # gamesurge: bungmoBott +#match: +# '^gamesurge|twitch$' +# '^#bungmonkey$' +# '^!help$': [ { perm: [ any ], func: say, arg: ['| https://bungmonkey.omgwallhack.org/txt/commands.txt'] } ] module BungmoBott EXE = "bungmobott" - + class ChatUser include YAML::Serializable include YAML::Serializable::Unmapped @@ -49,7 +53,7 @@ module BungmoBott property twitch : Array(String)? property gamesurge : Array(String)? end - + class Config include YAML::Serializable include YAML::Serializable::Unmapped @@ -74,7 +78,10 @@ module BungmoBott property obs_connect : String? = nil property chat_user : ChatUser? = ChatUser.from_yaml("---") property join_channels : JoinChannels? = JoinChannels.from_yaml("---") - property commands : Hash( String, Array( Commands ) )? = nil + property match : Hash( String, # network regex + Hash( String, # channel regex + Hash( String, # text regex + Array( Commands ) ) ) )? @[YAML::Field(emit_null: true)] property twitch_user_id : UInt32? = nil @[YAML::Field(emit_null: true)] diff --git a/crystal/tcpsocket.cr b/crystal/tcpsocket.cr index a7d1e8a..c9cdf84 100644 --- a/crystal/tcpsocket.cr +++ b/crystal/tcpsocket.cr @@ -297,7 +297,7 @@ fibers = Hash( String, Fiber ).new evchan = Channel( JSON::Any ).new obs : Nil | OBS::WebSocket = nil if config.obs_connect - obs = OBS::WebSocket.new( "ws://#{config.obs_connect}/" ) + obs = OBS::WebSocket.new( "ws://#{config.obs_connect}/", secrets.obs_password ) # OBS event thread spawn name: "obs_event_thread" do fiberipc.send( Fiber.current ) @@ -592,7 +592,6 @@ rescue ex return nil end - # Currently only used in flag?(:unix) def t2smsg( config : BungmoBott::Config, msg : String) if File.exists?( config.rundir + "/.t2s.sock" ) @@ -741,235 +740,236 @@ spawn name: "command_dispatch" do # end # end # end - config.commands && config.commands.not_nil!.each do | commandregex, command | # Hash( String, Array ) - if Regex.new( commandregex ).match( message.params[1] ) - command.each do |exec| # Array - if ( - ( botowner || ( exec.perm && exec.perm.not_nil!.includes?("any") ) ) || - ( chanowner && ( exec.perm && exec.perm.not_nil!.includes?("owner") ) ) || - ( mod && ( exec.perm && exec.perm.not_nil!.includes?("mod") ) ) || - ( sub && ( exec.perm && exec.perm.not_nil!.includes?("sub") ) ) || - ( vip && ( exec.perm && exec.perm.not_nil!.includes?("vip") ) ) - ) - # As a matter of policy: - # BungmoBott::Socket clients can only say things in their own authenticated channel - # Direct IRC clients can say things whereever. - # FIXME: this needs to be service-specific - if ( service =~ /^twitch/ && exec.func == "detect_rename" && uid.is_a?( UInt64 ) ) - unless oldname.is_a?( String ) - say( service, config.chat_user.not_nil!.twitch.not_nil!, "Rename detected: #{uid}: #{oldname} -> #{chatuser}" ) - end - next - end - if obs - # FIXME: better validate args - ( exec.func == "obs_random_source_enable" ) && obsrandommediaenable( obs, exec.arg.not_nil![0].not_nil! ) && next - if ( exec.func == "obs_stats_get" ) - puts "Exec-ing obs_stats_get" - stats = obs.stats.to_h - ostatus = obs.outputs["adv_stream"].status.to_h - say( service, ircchannel, "| #{ostatus["outputTimecode"].to_s[0..7]} #{stats["activeFps"].to_s[0,2]}fps Usage: #{stats["cpuUsage"].as(Float64).to_i64}% #{stats["memoryUsage"].as(Float64).to_i64}MiB Frame losses: #{stats["outputSkippedFrames"].as(Int64)} #{stats["renderSkippedFrames"].as(Int64)}" ) - next - end - if ( exec.func == "obs_scene_list_get" ) - puts "Exec-ing obs_scene_list_get" - say( service, ircchannel, "| scenes: #{obs.scenes.to_h.keys.join(" ")}" ) - next - end - if ( exec.func == "obs_scene_get" ) - puts "Exec-ing obs_scene_get" - say( service, ircchannel, "| Current scene: #{obs.scenes.program.name}" ) - next - end - if ( exec.func == "obs_scene_set" ) - puts "Exec-ing obs_scene_set" - - if ( match = / ([a-zA-Z0-9-_ ]+)/.match( message.params[1] ) ) - obs.scenes[match[1]].program! - else - say( service, ircchannel, "| Could not find scene name." ) + config.match && config.match.not_nil!.each do | networkregex, channelhash | + Regex.new( networkregex ).match( service ) || next + channelhash.each do | channelregex, texthash | + Regex.new( channelregex ).match( ircchannel ) || next + texthash.each do | textregex, command | # Hash( String, Array ) + Regex.new( textregex ).match( message.params[1] ) || next + command.each do |exec| # Array + if ( + ( botowner || ( exec.perm && exec.perm.not_nil!.includes?("any") ) ) || + ( chanowner && ( exec.perm && exec.perm.not_nil!.includes?("owner") ) ) || + ( mod && ( exec.perm && exec.perm.not_nil!.includes?("mod") ) ) || + ( sub && ( exec.perm && exec.perm.not_nil!.includes?("sub") ) ) || + ( vip && ( exec.perm && exec.perm.not_nil!.includes?("vip") ) ) + ) + # As a matter of policy: + # BungmoBott::Socket clients can only say things in their own authenticated channel + # Direct IRC clients can say things whereever. + # FIXME: this needs to be service-specific + if ( service =~ /^twitch/ && exec.func == "detect_rename" && uid.is_a?( UInt64 ) ) + unless oldname.is_a?( String ) + say( service, config.chat_user.not_nil!.twitch.not_nil!, "Rename detected: #{uid}: #{oldname} -> #{chatuser}" ) end next end - if ( exec.func == "obs_input_list_get" ) - puts "Exec-ing obs_input_list_get" - say( service, ircchannel, "| inputs: #{obs.inputs.to_h.keys.join(" ")}" ) - next - end - if ( exec.func == "obs_source_list_get" ) - puts "Exec-ing obs_source_list_get" - say( service, ircchannel, "| current sources: #{obs.scenes.current.metascene.keys.join(" ")}" ) - next - end - if ( exec.func == "obs_source_toggle" ) - puts "Exec-ing obs_source_toggle" - - if ( match = / ([a-zA-Z0-9-_ ]+)/.match( message.params[1] ) ) - obs.scenes.current.metascene[match[1]][0].toggle! - # in studio mode, direct Scene->SceneItem toggles require a transition - obs.scenes.current.preview! - obs.transition! - else - say( service, ircchannel, "| Could not find source name." ) + if obs + # FIXME: better validate args + ( exec.func == "obs_random_source_enable" ) && obsrandommediaenable( obs, exec.arg.not_nil![0].not_nil! ) && next + if ( exec.func == "obs_stats_get" ) + puts "Exec-ing obs_stats_get" + stats = obs.stats.to_h + ostatus = obs.outputs["adv_stream"].status.to_h + say( service, ircchannel, "| #{ostatus["outputTimecode"].to_s[0..7]} #{stats["activeFps"].to_s[0,2]}fps Usage: #{stats["cpuUsage"].as(Float64).to_i64}% #{stats["memoryUsage"].as(Float64).to_i64}MiB Frame losses: #{stats["outputSkippedFrames"].as(Int64)} #{stats["renderSkippedFrames"].as(Int64)}" ) + next end - next - end - if ( exec.func == "obs_source_filters_list_get" ) - puts "Exec-ing obs_source_filters_list_get" - if ( match = / ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) - say( service, ircchannel, "| #{match[1]} filters: #{obs.sources.to_h[match[1]].filters.to_h.keys.join(" ")}" ) - else - say( service, ircchannel, "| Could not match source name." ) + if ( exec.func == "obs_scene_list_get" ) + puts "Exec-ing obs_scene_list_get" + say( service, ircchannel, "| scenes: #{obs.scenes.to_h.keys.join(" ")}" ) + next end - next - end - if ( exec.func == "obs_source_filter_toggle" ) - puts "Exec-ing obs_source_filter_toggle" - if ( match = / ([a-zA-Z0-9-_]+) ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) - obs.sources.to_h[match[1]].filters[match[2]].toggle! - else - say( service, ircchannel, "| Could not match source and filter name to toggle." ) + if ( exec.func == "obs_scene_get" ) + puts "Exec-ing obs_scene_get" + say( service, ircchannel, "| Current scene: #{obs.scenes.program.name}" ) + next end - next - end - if ( exec.func == "obs_create_ephemeral_media_sources_from_path" ) - puts "Exec-ing obs_create_ephemeral_media_sources_from_path" - count = 1 - sources = file_list( exec.arg.not_nil![1].not_nil! ) - if ( match = / ([0-9]+)/.match( message.params[1] ) ) - count = match[1].to_i - if ( ( count < 1 ) || ( count > sources.size ) ) - say( service, ircchannel, "| Ephemeral OBS source count must be between 1 and #{sources.size}" ) - next + if ( exec.func == "obs_scene_set" ) + puts "Exec-ing obs_scene_set" + if ( match = / ([a-zA-Z0-9-_ ]+)/.match( message.params[1] ) ) + obs.scenes[match[1]].program! + else + say( service, ircchannel, "| Could not find scene name." ) end + next end - sources.sample( count ).each do |path| - obstemporarymediacreate( obs, exec.arg.not_nil![0].not_nil!, path.gsub(/[\/ ]/, '_').downcase, path ) + if ( exec.func == "obs_input_list_get" ) + puts "Exec-ing obs_input_list_get" + say( service, ircchannel, "| inputs: #{obs.inputs.to_h.keys.join(" ")}" ) + next end - next - end - - - end - if text2speech - if ( exec.func == "text_to_speech" ) - puts "Exec-ing text_to_speech" - if ( t2sreturn = t2s( t2sipc, config, userdir, chatuser, message.params[1] ) ) - lastvoice.insert( 0, t2sreturn.strip ) - lastvoice = lastvoice[0..4] + if ( exec.func == "obs_source_list_get" ) + puts "Exec-ing obs_source_list_get" + say( service, ircchannel, "| current sources: #{obs.scenes.current.metascene.keys.join(" ")}" ) + next end - next - end - if ( exec.func == "tts_voice_list_generate" ) - puts "Exec-ing " + exec.func - regeneratevoicelist() - say( service, ircchannel, "| Regenerated voicelist." ) - next - end - if ( exec.func == "tts_last_voice" ) - unless lastvoice.empty? - say( service, ircchannel, "| Last voices were " + lastvoice.join( ", " ) ) - else - say( service, ircchannel, "| No voices used so far." ) + if ( exec.func == "obs_source_toggle" ) + puts "Exec-ing obs_source_toggle" + if ( match = / ([a-zA-Z0-9-_ ]+)/.match( message.params[1] ) ) + obs.scenes.current.metascene[match[1]][0].toggle! + # in studio mode, direct Scene->SceneItem toggles require a transition + obs.scenes.current.preview! + obs.transition! + else + say( service, ircchannel, "| Could not find source name." ) + end + next + end + if ( exec.func == "obs_source_filters_list_get" ) + puts "Exec-ing obs_source_filters_list_get" + if ( match = / ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) + say( service, ircchannel, "| #{match[1]} filters: #{obs.sources.to_h[match[1]].filters.to_h.keys.join(" ")}" ) + else + say( service, ircchannel, "| Could not match source name." ) + end + next + end + if ( exec.func == "obs_source_filter_toggle" ) + puts "Exec-ing obs_source_filter_toggle" + if ( match = / ([a-zA-Z0-9-_]+) ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) + obs.sources.to_h[match[1]].filters[match[2]].toggle! + else + say( service, ircchannel, "| Could not match source and filter name to toggle." ) + end + next + end + if ( exec.func == "obs_create_ephemeral_media_sources_from_path" ) + puts "Exec-ing obs_create_ephemeral_media_sources_from_path" + count = 1 + sources = file_list( exec.arg.not_nil![1].not_nil! ) + if ( match = / ([0-9]+)/.match( message.params[1] ) ) + count = match[1].to_i + if ( ( count < 1 ) || ( count > sources.size ) ) + say( service, ircchannel, "| Ephemeral OBS source count must be between 1 and #{sources.size}" ) + next + end + end + sources.sample( count ).each do |path| + obstemporarymediacreate( obs, exec.arg.not_nil![0].not_nil!, path.gsub(/[\/ ]/, '_').downcase, path ) + end + next end - next - end - if ( exec.func == "tts_voice_get" ) - puts "Exec-ing tts_voice_get" - namesub, voice_setting, voice_output = getvoice( config.voice_list.not_nil!, userdir, chatuser ) - say( service, config.chat_user.not_nil!.twitch.not_nil!, "| Current voice is #{voice_setting}" ) - next end - if ( exec.func == "tts_voice_set" ) - puts "Exec-ing tts_voice_set" - if ( match = / ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) - voice = match[1].downcase - if voice =~ /disabled|null|disable|none|random/ - if File.exists?( userdir + "/voice" ) - File.delete( userdir + "/voice" ) - say( service, ircchannel, "| Voice for #{chatuser} is now random." ) + if text2speech + if ( exec.func == "text_to_speech" ) + puts "Exec-ing text_to_speech" + if ( t2sreturn = t2s( t2sipc, config, userdir, chatuser, message.params[1] ) ) + lastvoice.insert( 0, t2sreturn.strip ) + lastvoice = lastvoice[0..4] + end + next + end + if ( exec.func == "tts_voice_list_generate" ) + puts "Exec-ing " + exec.func + regeneratevoicelist() + say( service, ircchannel, "| Regenerated voicelist." ) + next + end + if ( exec.func == "tts_last_voice" ) + unless lastvoice.empty? + say( service, ircchannel, "| Last voices were " + lastvoice.join( ", " ) ) + else + say( service, ircchannel, "| No voices used so far." ) + end + next + end + if ( exec.func == "tts_voice_get" ) + puts "Exec-ing tts_voice_get" + namesub, voice_setting, voice_output = getvoice( config.voice_list.not_nil!, userdir, chatuser ) + say( service, config.chat_user.not_nil!.twitch.not_nil!, "| Current voice is #{voice_setting}" ) + next + end + if ( exec.func == "tts_voice_set" ) + puts "Exec-ing tts_voice_set" + if ( match = / ([a-zA-Z0-9-_]+)/.match( message.params[1] ) ) + voice = match[1].downcase + if voice =~ /disabled|null|disable|none|random/ + if File.exists?( userdir + "/voice" ) + File.delete( userdir + "/voice" ) + say( service, ircchannel, "| Voice for #{chatuser} is now random." ) + else + say( service, ircchannel, "| Voice for #{chatuser} is already random." ) + end + elsif voices.has_key?( voice.downcase ) + csvoice = voices[voice] + Dir.mkdir_p( userdir ) + File.write( userdir + "/voice", csvoice ) + pp userdir + say( service, ircchannel, "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" ).strip}." ) else - say( service, ircchannel, "| Voice for #{chatuser} is already random." ) + pp ( match ) + say( service, ircchannel, "| Invalid voice. To see list, use !voices" ) end - elsif voices.has_key?( voice.downcase ) - csvoice = voices[voice] - Dir.mkdir_p( userdir ) - File.write( userdir + "/voice", csvoice ) - pp userdir - say( service, ircchannel, "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" ).strip}." ) else - pp ( match ) - say( service, ircchannel, "| Invalid voice. To see list, use !voices" ) + say( service, ircchannel, "| tts_voice_set failed generic string match ") end - else - say( service, ircchannel, "| tts_voice_set failed generic string match ") + next end - next - end - if ( exec.func == "tts_name_get" ) - puts "Exec-ing " + exec.func - if File.exists?( userdir + "/voicesub" ) - say( service, ircchannel, "| Current name substitution is \"#{File.read( userdir + "/voicesub" ).strip}\"." ) - else - say( service, ircchannel, "| Current name substitution is disabled." ) + if ( exec.func == "tts_name_get" ) + puts "Exec-ing " + exec.func + if File.exists?( userdir + "/voicesub" ) + say( service, ircchannel, "| Current name substitution is \"#{File.read( userdir + "/voicesub" ).strip}\"." ) + else + say( service, ircchannel, "| Current name substitution is disabled." ) + end end - end - if ( exec.func == "tts_name_set" ) - puts "Exec-ing tts_name_set" - if ( match = / ([\sa-zA-Z0-9-]+)$/.match( message.params[1] ) ) - pp match[1] - voicesub = match[1].downcase - if voicesub =~ /^(disabled|null|disable|none)$/ - if File.exists?( userdir + "/voicesub" ) - File.delete( userdir + "/voicesub" ) - say( service, ircchannel, "| Name substitution for #{chatuser} is now disabled." ) + if ( exec.func == "tts_name_set" ) + puts "Exec-ing tts_name_set" + if ( match = / ([\sa-zA-Z0-9-]+)$/.match( message.params[1] ) ) + pp match[1] + voicesub = match[1].downcase + if voicesub =~ /^(disabled|null|disable|none)$/ + if File.exists?( userdir + "/voicesub" ) + File.delete( userdir + "/voicesub" ) + say( service, ircchannel, "| Name substitution for #{chatuser} is now disabled." ) + else + say( service, ircchannel, "| Name substitution for #{chatuser} is already disabled." ) + end else - say( service, ircchannel, "| Name substitution for #{chatuser} is already disabled." ) + Dir.mkdir_p( userdir ) + File.write( userdir + "/voicesub", voicesub ) + say( service, ircchannel, "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" ).strip}\"." ) end - else - Dir.mkdir_p( userdir ) - File.write( userdir + "/voicesub", voicesub ) - say( service, ircchannel, "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" ).strip}\"." ) end + next end + end + if ( exec.func == "say" ) + say( service, config.chat_user.not_nil!.twitch.not_nil!, exec.arg.not_nil![0].not_nil! ) next end - end - if ( exec.func == "say" ) - say( service, config.chat_user.not_nil!.twitch.not_nil!, exec.arg.not_nil![0].not_nil! ) - next - end - if ( exec.func == "run" ) - if ( cmd = exec.arg[0]? ) && File.executable?( cmd ) - p = Process.new( - cmd, exec.arg[1..]?, output: STDOUT, error: STDERR, - env: { - "TEXT" => message.params[1], - "CHATUSER" => chatuser - } - ) - else - STDERR.puts( "WARNING: exec.func \"run\" called without argument" ) + if ( exec.func == "run" ) + if ( cmd = exec.arg[0]? ) && File.executable?( cmd ) + p = Process.new( + cmd, exec.arg[1..]?, output: STDOUT, error: STDERR, + env: { + "TEXT" => message.params[1], + "CHATUSER" => chatuser + } + ) + else + STDERR.puts( "WARNING: exec.func \"run\" called without argument" ) + end end - end - if ( exec.func == "run_shell" ) - if ( cmd = exec.arg[0]? ) - p = Process.new( - cmd, exec.arg[1..]?, output: STDOUT, error: STDERR, shell: true, - env: { - "TEXT" => message.params[1], - "CHATUSER" => chatuser - } - ) - else - STDERR.puts( "WARNING: exec.func \"run_shell\" called without argument" ) + if ( exec.func == "run_shell" ) + if ( cmd = exec.arg[0]? ) + p = Process.new( + cmd, exec.arg[1..]?, output: STDOUT, error: STDERR, shell: true, + env: { + "TEXT" => message.params[1], + "CHATUSER" => chatuser + } + ) + else + STDERR.puts( "WARNING: exec.func \"run_shell\" called without argument" ) + end + next end - next + STDERR.puts "WARNING: unhandled function for /#{textregex}/: #{exec.func}" + else + STDOUT.print "DENIED: " + ppe command end - STDERR.puts "WARNING: unhandled function for /#{commandregex}/: #{exec.func}" - else - STDOUT.print "DENIED: " - ppe command end end end |