From 061b5e956e422d585f8733e8079c60a625c6d27f Mon Sep 17 00:00:00 2001 From: Joe Rayhawk Date: Tue, 27 Feb 2024 03:22:55 -0800 Subject: crystal/bungmobott: deprecate old codebase --- crystal/bungmobott.cr | 991 -------------------------------------------------- 1 file changed, 991 deletions(-) delete mode 100755 crystal/bungmobott.cr (limited to 'crystal/bungmobott.cr') diff --git a/crystal/bungmobott.cr b/crystal/bungmobott.cr deleted file mode 100755 index 642d0b3..0000000 --- a/crystal/bungmobott.cr +++ /dev/null @@ -1,991 +0,0 @@ -require "twitch/irc" -require "http" -require "uri" -require "twitcr" -require "json" -require "crystal_mpd" -require "obswebsocket" - -STDOUT.sync = true -STDOUT.flush_on_newline = 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?( Path.home./("/.aws/credentials") ) - STDERR.puts "Warning: #{Path.home}/.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 - -# enable effects? -effects = Hash( String, String ).new -if File.exists?( settings["configdir"] + "/effects.txt" ) - File.each_line( settings["configdir"] + "/effects.txt" ) do |line| - effects[ line.downcase ] = line - end -end - -client = Twitcr::Client.new( settings ) - -# derive channel_id from channel or vice versa -[ "channel", "channel_id" ].each do |key| - File.exists?( settings["configdir"] + key ) && ( settings[key] = File.read( settings["configdir"] + key ).chomp ) -end -if ( settings["channel"]? =~ regextwitchuser ) && ( settings["channel_id"]? =~ /^[0-9]+$/ ) -elsif ! ( settings["channel"]? =~ regextwitchuser ) && ( settings["channel_id"]? =~ /^[0-9]+$/ ) - settings["channel"] = client.user( settings["channel_id"].to_u64 ).login -elsif ( settings["channel"]? =~ regextwitchuser ) && ! ( settings["channel_id"]? =~ /^[0-9]+$/ ) - settings["channel_id"] = client.user( settings["channel"] ).id.to_s -else - STDERR.puts "ERROR: Missing #{settings["configdir"]}channel and channel_id configuration keys." - error = true - # exit 2 -end -if error == true - {% if flag?(:windows) %} - STDERR.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 "Warning: " + settings["configdir"] + "chat_user is missing; using configured channel instead." - settings["chat_user"] = settings["channel"] -end - -# fiber communication channels -ircipc = Channel( Tuple( String, String ) ).new -t2sipc = Channel( Tuple( String, String ) ).new -twitchipc = Channel( Tuple( String, Union( String, UInt64 ) ) ).new - -obs = OBS::WebSocket.new( "ws://127.0.0.1:4455/" ) - -# OBS event thread -spawn do - evchan = Channel( JSON::Any ).new - obs.scenes["meta-foreground"].to_h.each_key do | key | - if key =~ /^media-temporary-/ - obs.inputs[key].delete! - end - end - obs.eventsub_add( evchan ) - while json = evchan.receive - # A Fiber.yield occurs after this to make sure "json" doesn't get overwritten before we can use it. - spawn do - d = json # Copy *immediately* - case d["eventType"].as_s - when "CurrentProgramSceneChanged" - ircipc.send( { "#" + settings["channel"], "| obs: switched scene to " + ( d["eventData"]["sceneName"]?.as_s? || "unknown" ) } ) - when "MediaInputPlaybackEnded" - if d["eventData"]["inputName"].as_s =~ /^media-temporary-/ - obs.send( OBS.req( "RemoveInput", UUID.random.to_s, JSON.parse({ "inputName" => d["eventData"]["inputName"].as_s }.to_json) ) ) - elsif d["eventData"]["inputName"].as_s =~ /^media-/ - obs.scenes.current.metascene[d["eventData"]["inputName"].as_s][0].disable! - end - when "SceneItemEnableStateChanged" - edata = d["eventData"] - name = obs.scenes[ edata["sceneName"].as_s ][ edata["sceneItemId"].as_i64 ].name - if name !~ /media-temporary/ - ircipc.send( { "##{settings["channel"]}", "| obs: source #{name} visibility is now #{edata["sceneItemEnabled"].as_bool}" } ) - end - when "SceneItemTransformChanged" - edata = d["eventData"] - sceneitem = obs.scenes[edata["sceneName"].as_s][edata["sceneItemId"].as_i64] - t = edata["sceneItemTransform"] - if ( sceneitem.name =~ /^media-temporary-/ ) - spx = t["positionX" ].as_f.to_i64 - spy = t["positionY" ].as_f.to_i64 - sdx = t["sourceHeight"].as_f.to_i64 - sdy = t["sourceWidth" ].as_f.to_i64 - if ( spx == 0 && spy == 0 && sdx != 0 && sdy != 0 ) - # source position randomizer - bx = obs.video.to_h["baseWidth" ].as(Int64 | Float64).to_i64 - by = obs.video.to_h["baseHeight"].as(Int64 | Float64).to_i64 - spx = ( rand(bx) - (sdx / 2) ) - spy = ( rand(by) - (sdy / 2) ) - sceneitem.transform( { "positionX" => spx, "positionY" => spy } ) - end - end - when "SourceFilterEnableStateChanged" - edata = d["eventData"] - ircipc.send( { "##{settings["channel"]}", "| obs: source #{edata["sourceName"].as_s} filter #{edata["filterName"].as_s} visibility is currently #{edata["filterEnabled"].as_bool}" } ) - end - end - Fiber.yield - end -end - -# Twitch API request handling thread -spawn do - loop do - begin - while twitchtuple = twitchipc.receive - cmd, arg = [ *twitchtuple ] - case cmd - when "get_user" - userinfo = JSON.parse( client.get_user( arg ) )["data"][0] - pp userinfo - unless userinfo["broadcaster_type"].as_s.blank? - puts "\033[38;5;12m#{userinfo["login"]} is #{userinfo["broadcaster_type"]}\033[0m" - end - userage = ( Time.utc.to_unix - Time::Format::RFC_3339.parse( userinfo["created_at"].as_s ).to_unix ) - if ( userage - 172800 ) < 0 - puts "\033[38;5;1m#{userinfo["login"]}'s account is #{((172800 - userage)/60/60).to_i64} hours old.\033[0m" - end - when "get_followers" - followers = JSON.parse( client.get_user_follows( to: arg.to_u64 ) )["total"].as_i64 - if followers > 500 - puts "\033[38;5;2m#{followers} followers\033[0m" - end - end - end - end - end -end - -def obsrandommediaenable( obs : OBS::WebSocket, siname : String ) - if ( Random.rand(3) < 2 ) - obs.scenes.current.metascene[siname][0].enable! - else - randsiname = obs.scenes.current.metascene.keys.select( /^#{siname}/ ).sample( 1 )[0] - obs.scenes.current.metascene[randsiname][0].enable! - end -end - -def obstemporarymediacreate( obs : OBS::WebSocket, sname : String, iname, path : String ) - iname = "media-temporary-effect-#{iname}" - isettings = Hash( String, String | Bool | Int64 | Float64 ){ - "advanced" => true, - "clear_on_media_end" => true, - "color_range" => 0.to_i64, - "is_local_file" => true, - "looping" => false, - "restart_on_activate" => true, - "local_file" => path, - } - response = obs.scenes[sname].createinput( iname, "ffmpeg_source", isettings ) - # Skip ORM stuff and configure the SceneItem as fast as we possibly can - if ( rdata = response["responseData"]? ) && ( goodtransform = obs.sources.last_known_real_transform?( iname ) ) - siid = rdata["sceneItemId"].as_i64 - obs.send( OBS.req( "SetSceneItemTransform", UUID.random.to_s, JSON.parse( { "sceneName" => sname, "sceneItemId" => siid, "sceneItemTransform" => { "positionX" => goodtransform.to_h["positionX"], "positionY" => goodtransform.to_h["positionY"] } }.to_json ) ) ) - end -end - -def urbandef( term : String ) - ssl_context = OpenSSL::SSL::Context::Client.new - {% if flag?(:windows) %} - ssl_context.verify_mode = OpenSSL::SSL::VerifyMode::NONE - {% end %} - #https://api.urbandictionary.com/v0/define?term=waifu - response = HTTP::Client.exec( "GET", "https://api.urbandictionary.com/v0/define?term=#{term}", tls: ssl_context ) - puts response.status_code - json = JSON.parse( response.body ) - return json["list"][0]["definition"].to_s.gsub( /[\[\]]/, "" ).gsub( /(\r|\n)/, " " ) -end - -# Currently only used in flag?(: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 - if File.directory?( userdir ) - lastseen = File.info( userdir ).modification_time.to_unix - else - lastseen = 0 - end - Dir.mkdir_p( basedir + "/names" ) - Dir.mkdir_p( userdir + "/names" ) - File.touch( userdir ) - 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, lastseen } ) -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 "}, - { / { "text" => text }, - "audioConfig" => { "audioEncoding" => "MP3" }, - "voice" => { - "name" => voice, - "languageCode" => match[1], - }, - } - 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, request.to_json, tls: ssl_context ) - - response.body - - filepath="#{settings["tempdir"]}#{Time.utc.to_unix_ms}.mp3" - json=JSON.parse(response.body) - File.write( filepath, Base64.decode_string( json["audioContent"].as_s ) ) - playaudiofile( filepath ) - File.delete( filepath ) - elsif aws # AWS polly voices - filepath="#{settings["tempdir"]}#{Time.utc.to_unix_ms}.mp3" - p = Process.new( - "aws.exe", [ - "polly", "synthesize-speech", - "--output-format", "mp3", - "--voice-id", voice, - "--text", text, - filepath - ], output: STDOUT, error: STDERR - ) - p.wait - playaudiofile( filepath ) - File.delete( filepath ) - else # unknown - STDERR.puts "Voice not recognized or available." - end - end - rescue ex - puts ex - end - 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 + 12 ) ) - 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, lastseen = userlogreturn - if ( t2sreturn = t2s( t2sipc, settings, userdir, chatuser, message.params[1] ) ) - lastvoice.insert( 0, t2sreturn ) - lastvoice = lastvoice[0..4] - end - # Have we seen this user lately? - if ( ( Time.utc.to_unix - lastseen ) >= 14400 ) - twitchipc.send( { "get_user", uid.to_u64 } ) - twitchipc.send( { "get_followers", uid.to_u64 } ) - prevnames = Array( String ).new - if ( prevnames = Dir.children( settings["statedir"] + "/uids/" + uid + "/names" ) ) && ( prevnames.size > 1 ) - prevnames.delete( chatuser ) - puts "\033[38;5;14m#{chatuser} previous names: #{prevnames.join(", ")}\033[0m" - end - # play random fanfare if available. - {% if flag?(:windows) %} - # This file hierarchy gets manually set up for now. - # Maybe someday let mods do something like: - # !fanfare add pipne https://pip.ne/sniff.mp3 - if File.exists?( userdir + "/fanfare/" ) - playaudiofile( userdir + "/fanfare/" + Dir.children( userdir + "/fanfare/" ).sample ) - end - {% else %} - t2smsg( settings, "fanfare #{uid} #{chatuser}" ) - {% end %} - end - 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" ) - [ { 301501910, "farts" }, - { 322820, "explosions" } ].each do | fx | - emoteid = fx[0] - fxname = fx[1] - if ( message.tags["emotes"]? ) && - ( message.tags["emotes"] ) && - ( fxemotes = message.tags["emotes"].not_nil!.split("/").select( /^#{emoteid}[:_]/ ).join(",").split(",") ) && - ( ! fxemotes[0].empty? ) && - ( effects.values.select(/^#{fxname}/).size > 0 ) - effects.values.select(/^#{fxname}/).sample( fxemotes.size ).each do | filepath | - obstemporarymediacreate( obs, "meta-foreground", filepath.gsub(/[\/ ]/, '_').downcase, "C:/cygwin64/home/user/effects/#{filepath}" ) - end - end - end - case message.params[1] - when /pl(ease|z).+nerf/i - obsrandommediaenable( obs, "media-youdied-nerf" ) - when /you.+died/i - obsrandommediaenable( obs, "media-youdied" ) - when /did +nothing +wrong/i - obsrandommediaenable( obs, "media-youdied-tiggs" ) - when /thanks.+obama/i - obsrandommediaenable( obs, "media-youdied-thanksobama" ) - 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] - 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 == "regeneratevoicelist" ) && ( own ) ) - voices = regeneratevoicelist( settings, aws, gcloud ) - ircipc.send( { "##{settings["channel"]}", "| Regenerated voicelist." } ) - elsif ( ( cmd =~ /^(voices|voice(s|)list|listvoice(s|))$/ ) && ( 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" )}." ) - else - pp ( match ) - bot.message( "##{settings["channel"]}", "| Invalid voice. To see list, use !voices" ) - end - end - when 3 - else - end - elsif ( cmd =~ /^uptime$/ ) - stats = obs.stats.to_h - ostatus = obs.outputs["adv_stream"].status.to_h - ircipc.send( { "##{settings["channel"]}", "| #{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)}" } ) - # !newimage [name] [url] (random|top|bottom|left|right|center) - # download url, verify mimetype with libmagic, duplicate existing medialoop source, change "file" SourceSetting - elsif ( ( cmd =~ /^inputsetting(|s)$/ ) && ( mod || own || vip ) ) - if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) - ircipc.send( { "##{settings["channel"]}", "| #{obs.inputs[match[2]].settings.to_h.to_s} " } ) - end - elsif ( ( cmd =~ /^input(|s)$/ ) && ( mod || own || vip ) ) - ircipc.send( { "##{settings["channel"]}", "| inputs: #{obs.inputs.to_h.keys.join(" ")}" } ) - elsif ( ( cmd =~ /^scene(|s)$/ ) && ( mod || own || vip ) ) - if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) - obs.scenes[match[2]].program! - else - ircipc.send( { "##{settings["channel"]}", "| scenes: #{obs.scenes.to_h.keys.join(" ")}" } ) - end - elsif ( ( cmd =~ /^source(|s)$/ ) && ( mod || own || vip ) ) - request = Hash( String, String | Bool ).new - if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) - obs.scenes.current.metascene[match[2]][0].toggle! - # in studio mode, direct Scene->SceneItem toggles require a transition - obs.scenes.current.preview! - obs.transition! - else - ircipc.send( { "##{settings["channel"]}", "| current sources: #{obs.scenes.current.metascene.keys.join(" ")}" } ) - end - elsif ( ( cmd =~ /^filter(|s)$/ ) && ( mod || own ) ) - if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+)/ ) ) - obs.sources.to_h[filterargs[1]].filters[filterargs[2]].toggle! - elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) - ircipc.send( { "##{settings["channel"]}", "| #{match[2]} filters: #{obs.sources.to_h[match[2]].filters.to_h.keys.join(" ")}" } ) - else - ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument, and optionally one filter name to toggle on or off." } ) - end - elsif ( cmd == "create" && ( mod || own || vip ) ) - if ( match[2]? ) && ( match[2] =~ /^([\/a-zA-Z0-9-_]+)/ ) - obstemporarymediacreate( obs, "meta-foreground", match[2], "C:/cygwin64/home/user/effects/#{match[2]}.webm" ) - else - ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } ) - end - elsif ( cmd =~ /^(metaminute|youdied)$/ ) - obsrandommediaenable( obs, "media-#{cmd}" ) - elsif ( cmd =~ /^fart(s|)$/ ) && ( sub || mod || own || vip ) - if effects.values.select( /^farts/ ).size > 0 - if ( match[2]? ) && ( match[2].match( /^([0-9]+)/ ) && ( match[2].to_i > 1 ) && ( match[2].to_i <= 32 ) ) - count = match[2].to_i - else - count = 1 - end - effects.values.select( /^farts/ ).sample( count ).each do | effect | - obstemporarymediacreate( obs, "meta-foreground", effect.gsub(/[\/ ]/, '_').downcase, "C:/cygwin64/home/user/effects/#{effect}" ) - end - else - puts "farts undefined" - end - elsif ( cmd =~ /^explosion(s|)$/ ) && ( sub || mod || own || vip ) - if effects.values.select( /^explosions/ ).size > 0 - if ( match[2]? ) && ( match[2].match( /^([0-9]+)/ ) && ( match[2].to_i > 1 ) && ( match[2].to_i <= 32 ) ) - count = match[2].to_i - else - count = 1 - end - effects.values.select( /^explosions/ ).sample( count ).each do | effect | - obstemporarymediacreate( obs, "meta-foreground", effect.gsub(/[\/ ]/, '_').downcase, "C:/cygwin64/home/user/effects/#{effect}" ) - end - else - puts "explosions undefined" - end - # FIXME: This is only half-implemented - elsif ( ( cmd =~ /^(user)$/ ) && ( mod || own ) ) - if match[2]? && match[2] =~ /[a-z][a-z0-9_]+/ - twitchipc.send( { "get_user", match[2] } ) - elsif match[2]? && match[2] =~ /[0-9]+/ - twitchipc.send( { "get_user", match[2].to_u64 } ) - else - twitchipc.send( { "get_user", settings["channel_id"].to_u64 } ) - end - elsif ( ( cmd =~ /^(status|title)$/ ) && ( mod || own ) ) - 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 - elsif ( ( cmd =~ /^(game|category)$/ ) && ( mod || own ) ) - 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 - 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] } ) - t2s( t2sipc, settings, userdir, chatuser, 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]}" ) - end - elsif ( cmd =~ /^(shout|shoutout)$/ ) - if match[2]? && match[2] =~ /^[a-z]+$/ - ircipc.send( { "##{settings["channel"]}", "| Go check out twitch.tv/#{match[2]}"} ) - effectsmsg( settings, "overlay gltext 5 Go follow #{match[2]}" ) - else - ircipc.send( { "##{settings["channel"]}", "| Missing argument."} ) - end - elsif ( cmd =~ /^(system|dxdiag|computer)$/ ) && ( Crystal::DESCRIPTION =~ /linux/ ) - ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/tmp/DxDiag.txt" } ) - 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" ) - 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 - end - rescue ex - puts ex - ircipc.send( { "##{settings["channel"]}", "An error occurred! " + ex.message.to_s } ) - 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"] -- cgit v1.2.3