twitch_chatter 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/twitch_chatter/bot.rb +162 -0
- data/lib/twitch_chatter/models/channel.rb +50 -0
- data/lib/twitch_chatter/models/message.rb +72 -0
- data/lib/twitch_chatter/models.rb +6 -0
- data/lib/twitch_chatter.rb +0 -1
- metadata +27 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65e839a944e5c1d2d0bf8049e954c12435872fb290c0027f6b42764375f8a61a
|
4
|
+
data.tar.gz: 0baa96f98a7b0161e44da032bb4c9387faf6c36aacda7be36924cc35f974035d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd559c343272abf40568722b859ecc73806dcb0627bf05e53f3280fe3590c09df9a4e146300353f619a54936c227dca5c5dab9729f6f66f2e70250f1b40524dc
|
7
|
+
data.tar.gz: 622feea1abcd924c2ff8d2fd6310685d5b38f947b2d0e806e46e2e3c0f93ae3fdba52a4b1fcdf48006763b1d0ea472d78becf5969f6928bc6b7f1bcc7dcc36f4
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twitch
|
4
|
+
# @example
|
5
|
+
# bot = Twitch::Bot.new
|
6
|
+
#
|
7
|
+
# bot.ready do
|
8
|
+
# puts "Ready!"
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# bot.join(:twitchgaming) do |message|
|
12
|
+
# puts "#{message.channel} #{message.user}: #{message.text}"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# bot.start
|
16
|
+
class Bot
|
17
|
+
# Default options for Twitch websocket. Nicknames starting with "justinfan" are considered anonymous.
|
18
|
+
# @api private
|
19
|
+
DEFAULT_OPTIONS = {
|
20
|
+
nick: "justinfan0735",
|
21
|
+
websocket_url: "wss://irc-ws.chat.twitch.tv:443",
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(**options)
|
25
|
+
@nick = options[:nick] || DEFAULT_OPTIONS[:nick]
|
26
|
+
@websocket_url = Async::HTTP::Endpoint.parse(options[:websocket_url] || DEFAULT_OPTIONS[:websocket_url])
|
27
|
+
end
|
28
|
+
|
29
|
+
# @yield
|
30
|
+
# @yieldparam
|
31
|
+
def ready(&block)
|
32
|
+
@ready_handle = block
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Boolean]
|
36
|
+
def ready?
|
37
|
+
@ws != nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Async::Task]
|
41
|
+
def start
|
42
|
+
Async do
|
43
|
+
Async::WebSocket::Client.connect(@websocket_url) do |ws|
|
44
|
+
@ws = ws
|
45
|
+
ws.write("NICK #{@nick}")
|
46
|
+
dispatch_ready
|
47
|
+
|
48
|
+
while (ws_message = ws.read)
|
49
|
+
data = ws_message.to_str
|
50
|
+
if data.start_with?("PING")
|
51
|
+
ws.write("PONG :tmi.twitch.tv")
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
next unless data.include?("PRIVMSG")
|
56
|
+
|
57
|
+
dispatch(Message.new(data, connection: self))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Array<Symbol>]
|
64
|
+
def channels
|
65
|
+
streams.keys
|
66
|
+
end
|
67
|
+
|
68
|
+
# @yield
|
69
|
+
# @yieldparam streamer [Symbol]
|
70
|
+
# @yieldreturn [nil]
|
71
|
+
def joined(&block)
|
72
|
+
@join_handle = block
|
73
|
+
end
|
74
|
+
|
75
|
+
# @yield
|
76
|
+
# @yieldparam streamer [Symbol]
|
77
|
+
# @yieldreturn [nil]
|
78
|
+
def left(&block)
|
79
|
+
@leave_handle = block
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param streamer [Symbol, String]
|
83
|
+
# @yield
|
84
|
+
# @yieldparam message [Twitch::Message]
|
85
|
+
# @yieldreturn [nil]
|
86
|
+
# @example
|
87
|
+
# bot.join(:twitchgaming) do |message|
|
88
|
+
# puts "##{message.channel} #{message.sender}: #{message}"
|
89
|
+
# end
|
90
|
+
def join(streamer, &block)
|
91
|
+
streamer = streamer.to_sym
|
92
|
+
unless ready?
|
93
|
+
yield_join(streamer, &block)
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
streams[streamer] = [] unless streams.key?(streamer)
|
98
|
+
streams[streamer] << block if block_given?
|
99
|
+
@ws.write("JOIN ##{streamer}")
|
100
|
+
@join_handle&.call(streamer)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param streamer [Symbol, String]
|
104
|
+
# @example
|
105
|
+
# bot.leave(:twitchgaming)
|
106
|
+
def leave(streamer)
|
107
|
+
streamer = streamer.to_sym
|
108
|
+
streams[streamer] = []
|
109
|
+
@ws.write("PART ##{streamer}")
|
110
|
+
@leave_handle&.call(streamer)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @yield
|
114
|
+
# @yieldparam message [Twitch::Message]
|
115
|
+
# @yieldreturn [nil]
|
116
|
+
# @example
|
117
|
+
# bot.message do |message|
|
118
|
+
# puts "##{message.channel} #{message.sender}: #{message}"
|
119
|
+
# end
|
120
|
+
def message(&block)
|
121
|
+
@message_handle = block
|
122
|
+
end
|
123
|
+
|
124
|
+
alias_method :part, :leave
|
125
|
+
alias_method :on_leave, :leave
|
126
|
+
alias_method :on_join, :joined
|
127
|
+
alias_method :on_message, :message
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def dispatch(message)
|
132
|
+
@message_handle&.call(message)
|
133
|
+
streams[message.channel.to_sym].each do |block|
|
134
|
+
block.call(message)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def dispatch_ready
|
139
|
+
join_all
|
140
|
+
@ready_handle&.call
|
141
|
+
end
|
142
|
+
|
143
|
+
def streams
|
144
|
+
@streams ||= {}
|
145
|
+
end
|
146
|
+
|
147
|
+
def pending
|
148
|
+
@pending ||= {}
|
149
|
+
end
|
150
|
+
|
151
|
+
def yield_join(streamer, &block)
|
152
|
+
pending[streamer] = [] if streamer
|
153
|
+
pending[streamer] << block
|
154
|
+
end
|
155
|
+
|
156
|
+
def join_all
|
157
|
+
pending.each do |streamer, blocks|
|
158
|
+
blocks.each { |block| join(streamer, &block) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twitch
|
4
|
+
# @!attribute [r] name
|
5
|
+
# @return [Symbol] Name of the channel
|
6
|
+
# @example
|
7
|
+
# channel = Twitch::Channel.new("twitchgaming")
|
8
|
+
class Channel
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# @param name [String, Symbol]
|
12
|
+
# @param connection [Bot, nil] For internal usage
|
13
|
+
def initialize(name, connection: nil)
|
14
|
+
@name = name.to_sym
|
15
|
+
@connection = connection
|
16
|
+
end
|
17
|
+
|
18
|
+
# @yield
|
19
|
+
# @yieldparam message [Twitch::Message]
|
20
|
+
# @return [nil]
|
21
|
+
def join(&block)
|
22
|
+
@connection.join(@name, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [nil]
|
26
|
+
def leave
|
27
|
+
@connection.leave(@name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Symbol]
|
31
|
+
def to_sym
|
32
|
+
@name
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
def to_s
|
37
|
+
@name.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param other [Channel, String, Symbol]
|
41
|
+
# @return [Boolean]
|
42
|
+
def ==(other)
|
43
|
+
if other.is_a?(Channel)
|
44
|
+
return @name == other.name
|
45
|
+
end
|
46
|
+
|
47
|
+
@name == other.to_sym
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twitch
|
4
|
+
# @!attribute [r] sender
|
5
|
+
# @return [Channel] Sender of the message
|
6
|
+
# @!attribute [r] channel
|
7
|
+
# @return [Channel] Channel the message was sent to
|
8
|
+
# @!attribute [r] content
|
9
|
+
# @return [String] Content of the message
|
10
|
+
# @!attribute [r] raw
|
11
|
+
# @return [String] Raw message from Twitch
|
12
|
+
# @example
|
13
|
+
# message = Twitch::Message.new(":justinfan!justinfan@justinfan.tmi.twitch.tv PRIVMSG #twitchgaming :Hello world!\r\n")
|
14
|
+
# message.sender.name # => :justinfan
|
15
|
+
# message.channel.name # => :twitchgaming
|
16
|
+
# message.content # => "Hello world!"
|
17
|
+
# message.raw # => ":justinfan!justinfan@justinfan.tmi.twitch.tv PRIVMSG #justinfan :Hello world!\r\n"
|
18
|
+
class Message < String
|
19
|
+
attr_reader :sender, :channel, :content, :raw
|
20
|
+
|
21
|
+
# @param raw [String] Raw IRC websocket message from Twitch
|
22
|
+
# @param connection [Bot, nil] For internal usage
|
23
|
+
# @example
|
24
|
+
# message = Twitch::Message.new(":justinfan!justinfan@justinfan.tmi.twitch.tv PRIVMSG #twitchgaming :Hello world!\r\n")
|
25
|
+
def initialize(raw, connection: nil)
|
26
|
+
split = raw.split(" ")
|
27
|
+
content = split[3..-1].join(" ")[1..-1]
|
28
|
+
super(content)
|
29
|
+
|
30
|
+
@content = content
|
31
|
+
@connection = connection
|
32
|
+
@channel = Channel.new(split[2][1..-1], connection: connection)
|
33
|
+
@sender = Channel.new(split[0].split("!")[0][1..-1])
|
34
|
+
@raw = raw
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :text, :content
|
38
|
+
alias_method :streamer, :channel
|
39
|
+
|
40
|
+
USERNAME = /[a-zA-Z0-9_]{4,25}/
|
41
|
+
# @return [Array<Twitch::Channel>] List of usernames mentioned in the message
|
42
|
+
def mentions
|
43
|
+
return @mentions if @mentions
|
44
|
+
|
45
|
+
@mentions = []
|
46
|
+
@content.split(" ").each do |word|
|
47
|
+
match = word.match(/@#{USERNAME}/)
|
48
|
+
next unless match
|
49
|
+
|
50
|
+
@mentions << Channel.new(match[0][1..-1], connection: @connection)
|
51
|
+
end
|
52
|
+
|
53
|
+
@mentions
|
54
|
+
end
|
55
|
+
|
56
|
+
LINK = %r{(https?://[^\s]+)}
|
57
|
+
# @return [Array<String>] List of links mentioned in the message
|
58
|
+
def links
|
59
|
+
return @links if @links
|
60
|
+
|
61
|
+
@links = []
|
62
|
+
@content.split(" ").each do |word|
|
63
|
+
match = word.match(LINK)
|
64
|
+
next unless match
|
65
|
+
|
66
|
+
@links << match[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
@links
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/twitch_chatter.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twitch_chatter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dylan Hackworth
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: async-websocket
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.26.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.26.1
|
13
27
|
description: A Ruby gem for Twitch's chat IRC websocket
|
14
28
|
email: me@dylhack.dev
|
15
29
|
executables: []
|
@@ -17,12 +31,16 @@ extensions: []
|
|
17
31
|
extra_rdoc_files: []
|
18
32
|
files:
|
19
33
|
- lib/twitch_chatter.rb
|
20
|
-
|
34
|
+
- lib/twitch_chatter/bot.rb
|
35
|
+
- lib/twitch_chatter/models.rb
|
36
|
+
- lib/twitch_chatter/models/channel.rb
|
37
|
+
- lib/twitch_chatter/models/message.rb
|
38
|
+
homepage: https://github.com/dylhack/twitch_chatter
|
21
39
|
licenses:
|
22
40
|
- MIT
|
23
41
|
metadata:
|
24
|
-
source_code_uri: https://github.com/dylhack/
|
25
|
-
post_install_message:
|
42
|
+
source_code_uri: https://github.com/dylhack/twitch_chatter
|
43
|
+
post_install_message:
|
26
44
|
rdoc_options: []
|
27
45
|
require_paths:
|
28
46
|
- lib
|
@@ -30,7 +48,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
48
|
requirements:
|
31
49
|
- - ">="
|
32
50
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.1.
|
51
|
+
version: 3.1.1
|
34
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
53
|
requirements:
|
36
54
|
- - ">="
|
@@ -38,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
56
|
version: '0'
|
39
57
|
requirements: []
|
40
58
|
rubygems_version: 3.5.9
|
41
|
-
signing_key:
|
59
|
+
signing_key:
|
42
60
|
specification_version: 4
|
43
61
|
summary: Read-only Twitch chat client.
|
44
62
|
test_files: []
|