require "http/web_socket" require "json" require "pretty_print" settings = Hash(String, String).new settings["home"] = Path.home.to_s settings["access_token"] = File.read( settings["home"] + "/.config/twitch/access_token" ).chomp settings["client_id"] = File.read( settings["home"] + "/.config/twitch/client_id" ).chomp settings["channel"] = File.read( settings["home"] + "/.config/twitch/channel" ).chomp settings["channel_id"] = File.read( settings["home"] + "/.config/twitch/channel_id" ).chomp struct Nil def to_s? self end end struct Nil def as_s? self end end struct Nil def as_i? self end end macro get_winsz() # Crystal doesn't support the TIOCGWINSZ ioctl `stty size`.split[1].to_i end def pubsub_send ( settings, pubsub : HTTP::WebSocket, msg ) pubsub.send( msg ) puts( ("SENT : " + msg).gsub(/#{settings["access_token"]}/,"NOPE").gsub(/#{settings["client_id"]}/, "NOPE") ) end def pubsub_listen ( settings : Hash, topic : String, channel_id : String = settings["channel_id"]) return( { "type" => "LISTEN", "nonce" => "listen-" + topic, "data" => { "auth_token" => settings["access_token"], "topics" => [ topic + "." + channel_id ] } }.to_json.to_s ) end #begin def ircmsg( home : String, channel : String, msg : String) if msg !~ /(\r|\n)/ sock = Socket.unix # sock.connect Socket::UNIXAddress.new( home + "/.irssi/twitch-socket".to_s, type: Socket::Type::DGRAM ) sock.connect Socket::UNIXAddress.new( home + "/.irssi/twitch-socket".to_s ) sock.puts( "#" + channel + " " + "pubsub: " + msg ) sock.close end end def effectsmsg( msg : String ) sock = Socket.unix sock.connect Socket::UNIXAddress.new( "/etc/twitch/effects-socket" ) sock.puts( msg ) sock.close rescue ex STDERR.puts( ex ) end msg_ping = { "type" => "PING" }.to_json.to_s pubsub = HTTP::WebSocket.new( URI.parse( "wss://pubsub-edge.twitch.tv" ), HTTP::Headers{"Cookie" => "SESSIONID=1234"} ) pubsub.on_message do | message | print( "RECEIVED: ") json = JSON.parse( message ) print( json.to_pretty_json ) print( "\n") case json["type"].not_nil! when "RESPONSE" when "PONG" when "MESSAGE" msg = JSON.parse( json["data"]["message"].not_nil!.to_s ) case json["data"]["topic"].not_nil! when "channel-bits-events-v1." + settings["channel_id"] bits_username = ( msg["data"]["user_name"]?.as_s? || "unknown" ) bits_used = ( msg["data"]["bits_used"]?.as_i?.to_s || "a quantity" ) bits_message = ( msg["data"]["chat_message"]?.as_s? || "" ) ircmsg( settings["home"], settings["channel"], "| " + bits_username.to_s + " sends " + bits_used.to_s + " bits: " + bits_message.to_s ) when "channel-subscribe-events-v1." + settings["channel_id"] sub_sender = ( msg["user_name"]?.as_s? || "anonymous" ) sub_recver = ( msg["recipient_user_name"]?.as_s? || sub_sender ) sub_plan = ( msg["sub_plan"]?.as_s? || "unknown" ) sub_months = ( msg["cumulative_months"]?.as_i? || 0 ) sub_msg = ( msg["sub_message"]["message"]?.as_s? || "" ) effectsmsg( "overlay gltext 10 " + sub_recver + " subscribed!" ) pp msg["is_gift"] if ( msg["is_gift"]? ) ircmsg( settings["home"], settings["channel"], sub_recver.to_s + " has subscribed for " + sub_months.to_s + " months at sub level #" + sub_plan.to_s + ": " + sub_msg.to_s ) else ircmsg( settings["home"], settings["channel"], sub_recver.to_s + " has subscribed for " + sub_months.to_s + " months at sub level #" + sub_plan.to_s + "thanks to a gift from " + sub_sender.to_s + ": " + sub_msg.to_s ) end # when "channel-commerce-events-v1." + settings["channel_id"] # "deprecated" which means "completely unsupported" because Twitch when "channel-bits-badge-unlocks." + settings["channel_id"] when "channel-points-channel-v1" + settings["channel_id"] # new scope; fix later when "chat_moderator_actions." + settings["channel_id"] when "whispers." + settings["channel_id"] end end end [ "channel-bits-events-v1", "channel-subscribe-events-v1", "channel-commerce-events-v1", # "deprecated", which means "completely unsupported" because Twitch "channel-bits-badge-unlocks", "channel-points-channel-v1", # requires new scope, fix later "chat_moderator_actions", "whispers", ].each do |topic| pubsub_send( settings, pubsub, pubsub_listen( settings, topic ) ) end spawn do loop do pubsub_send( settings, pubsub, msg_ping ) sleep 240 end end begin pubsub.run rescue ex ircmsg( settings["home"], settings["channel"], "pubsub.cr: " + ex.to_s.gsub(/\r|\n/, ' ') ) puts ex end #RECEIVED: { # "type": "MESSAGE", # "data": { # "topic": "channel-subscribe-events-v1.59895482", # "message": "{\"benefit_end_month\":0,\"user_name\":\"orangesherbet\",\"display_name\":\"orangesherbet\",\"channel_name\":\"bungmonkey\",\"user_id\":\"36184711\",\"channel_id\":\"59895482\",\"time\":\"2020-05-06T06:55:31.362125005Z\",\"sub_message\":{\"message\":\"seqDag bungmoButt bungmoFart\",\"emotes\":[{\"start\":0,\"end\":5,\"id\":\"496082\"},{\"start\":7,\"end\":16,\"id\":\"300780134\"},{\"start\":18,\"end\":27,\"id\":\"301501910\"}]},\"sub_plan\":\"1000\",\"sub_plan_name\":\"5\",\"months\":0,\"cumulative_months\":21,\"context\":\"resub\"}" # } #} # #RECEIVED: { # "type": "MESSAGE", # "data": { # "topic": "channel-bits-events-v1.59895482", # "message": "{ #\"data\":{\"user_name\":\"vchen1996\",\"channel_name\":\"bungmonkey\",\"user_id\":\"509868211\",\"channel_id\":\"59895482\",\"time\":\"2021-11-11T08:46:03.117271456Z\",\"chat_message\":\"Cheer100 how does one tickle the streamer or the teammate, please advise.\",\"bits_used\":100,\"total_bits_used\":200,\"context\":\"cheer\",\"badge_entitlement\":null,\"badge_tier_entitlement\":{\"Badge\":{\"new_version\":0,\"previous_version\":0},\"Emoticons\":null}},\"version\":\"1.0\",\"message_type\":\"bits_event\",\"message_id\":\"ad0da33a-54f4-5353-b806-6fcdcdc6eb98\"}" # } #}