twitch-bot 2.0.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b04ed5b85125452e5c58a1d4c48d308ca7d72000f09ae9768773287b242d91bf
4
- data.tar.gz: a9251b16f1f8d67f8e3fb8f5ce8608628cb7649306adbbb60d6afa1119ced08d
3
+ metadata.gz: e127546d8abb6ccd893003d8cbbac18fbce0bb31ca01070ace83b396100e3b63
4
+ data.tar.gz: '0998e3ee31311c74981e00f1b1a846cfc5f6d09a18b5fd08cd8b565bcadabf33'
5
5
  SHA512:
6
- metadata.gz: b831153426b24bb9af05ec085fe567c41d169e559560394a5867e95371074319c6ed1d9ea7551d52c8f74e5e6ed5ef901334e427234f19e80a1a48a24faaac9d
7
- data.tar.gz: 2fb72af43444bf1ec0eeb978690150da15c9b8a1527db16423da0bde6e4a98a06df2ea2bf0834563ec00da839289960cb7ba9557733e7ab3ee3bb1283c9b14d8
6
+ metadata.gz: c7cfdae853bc0e994617c273ab6ba02496c0f55cbf92ba67ef4af4e46d5704e355b5bd2fa5bb677adff39d1d8e8723d0d846f9548af34998f8547282d46e71a2
7
+ data.tar.gz: 644b140f23c27c178b243b91ccf2ddc797c49611f83cbfccdd8987b10c79573413feae4a72fdd7956169aef897be664a4ad36c50ff7e2bd66c7c67711f29df04
data/.gitignore CHANGED
@@ -12,7 +12,7 @@
12
12
  *.so
13
13
  *.o
14
14
  *.a
15
- mkmf.log
15
+ *.log
16
16
 
17
17
  # rspec failure tracking
18
18
  .rspec_status
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Twitch
6
+ module Bot
7
+ module Adapter
8
+ # Handle the bot's IRC connection
9
+ class Irc
10
+ def initialize(client:)
11
+ @client = client
12
+
13
+ open_socket
14
+ end
15
+
16
+ def connect
17
+ enable_twitch_capabilities
18
+ authenticate
19
+ end
20
+
21
+ def shutdown
22
+ close_socket
23
+ end
24
+
25
+ def read_data
26
+ raw_message = read_data_from_socket
27
+ irc_message = IrcMessage.new(raw_message)
28
+ Twitch::Bot::MessageParser.new(irc_message).message
29
+ end
30
+
31
+ def send_message(text)
32
+ privmsg = "PRIVMSG ##{channel.name} :#{text}"
33
+ send_data(privmsg)
34
+ end
35
+
36
+ def send_data(data)
37
+ send_data_to_socket(data)
38
+ end
39
+
40
+ def join_channel(channel)
41
+ @channel = channel
42
+ send_data "JOIN ##{channel.name}"
43
+ end
44
+
45
+ def part_channel
46
+ send_data "PART ##{channel.name}"
47
+ @channel = nil
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :socket, :client, :channel
53
+
54
+ def open_socket
55
+ connection = client.connection
56
+ @socket = ::TCPSocket.new(
57
+ connection.hostname,
58
+ connection.port,
59
+ )
60
+
61
+ Twitch::Bot::Logger.debug "Socket open"
62
+ end
63
+
64
+ def close_socket
65
+ socket.close
66
+ Twitch::Bot::Logger.debug "Socket closed"
67
+ end
68
+
69
+ def read_data_from_socket
70
+ Twitch::Bot::Logger.debug "Reading socket..."
71
+ data = socket_read_next
72
+ Twitch::Bot::Logger.debug "< #{data}"
73
+ data
74
+ end
75
+
76
+ # Acceptable :reek:NilCheck
77
+ def socket_read_next
78
+ loop do
79
+ line = socket.gets&.chomp
80
+ break unless line.nil? || line.empty?
81
+ end
82
+ line
83
+ end
84
+
85
+ def send_data_to_socket(data)
86
+ Twitch::Bot::Logger.debug "> #{sanitize_data(data)}"
87
+ socket.puts(data)
88
+ end
89
+
90
+ def enable_twitch_capabilities
91
+ send_data <<~DATA
92
+ CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership
93
+ DATA
94
+ end
95
+
96
+ def authenticate
97
+ connection = client.connection
98
+ send_data "PASS #{connection.password}"
99
+ send_data "NICK #{connection.nickname}"
100
+ end
101
+
102
+ def sanitize_data(data)
103
+ data.gsub(/(PASS oauth:)(\w+)/) do
104
+ "#{Regexp.last_match(1)}#{'*' * Regexp.last_match(2).size}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twitch
4
+ module Bot
5
+ module Adapter
6
+ # This adapter connects the chat client to the terminal
7
+ class Terminal
8
+ def initialize(client:)
9
+ @client = client
10
+ end
11
+
12
+ def connect; end
13
+
14
+ def shutdown; end
15
+
16
+ def read_data
17
+ read_message_from_terminal
18
+ end
19
+
20
+ def send_message(text)
21
+ send_data(text)
22
+ end
23
+
24
+ def send_data(data)
25
+ puts sanitize_data(data)
26
+ end
27
+
28
+ def join_channel(_channel); end
29
+
30
+ def part_channel; end
31
+
32
+ private
33
+
34
+ attr_reader :client
35
+
36
+ def sanitize_data(data)
37
+ data.gsub(/(PASS oauth:)(\w+)/) do
38
+ "#{Regexp.last_match(1)}#{'*' * Regexp.last_match(2).size}"
39
+ end
40
+ end
41
+
42
+ def read_message_from_terminal
43
+ Twitch::Bot::Logger.debug "Waiting for input..."
44
+ input = gets
45
+ Twitch::Bot::Message::UserMessage.new(
46
+ text: input,
47
+ user: "tester",
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "logger"
4
- require "socket"
5
4
 
6
5
  Thread.abort_on_exception = true
7
6
 
@@ -9,49 +8,6 @@ module Twitch
9
8
  module Bot
10
9
  # Twitch chat client object
11
10
  class Client
12
- MODERATOR_MESSAGES_COUNT = 100
13
- USER_MESSAGES_COUNT = 20
14
- TWITCH_PERIOD = 30.0
15
-
16
- # Respond to a :ping event with a pong so we don't get disconnected.
17
- class PingHandler < Twitch::Bot::EventHandler
18
- def call
19
- client.send_data "PONG :#{event.hostname}"
20
- end
21
-
22
- def self.handled_events
23
- [:ping]
24
- end
25
- end
26
-
27
- # Handle the :authenticated event required for joining our channel.
28
- class AuthenticatedHandler < Twitch::Bot::EventHandler
29
- def call
30
- client.join_default_channel
31
- end
32
-
33
- def self.handled_events
34
- [:authenticated]
35
- end
36
- end
37
-
38
- # Handle a change in moderators on the channel.
39
- class ModeHandler < Twitch::Bot::EventHandler
40
- def call
41
- user = event.user
42
- case event.mode
43
- when :add_moderator
44
- client.add_moderator(user)
45
- when :remove_moderator
46
- client.remove_moderator(user)
47
- end
48
- end
49
-
50
- def self.handled_events
51
- [:mode]
52
- end
53
- end
54
-
55
11
  # Represent the event triggered when quitting the client loop.
56
12
  class StopEvent < Twitch::Bot::Event
57
13
  def initialize
@@ -59,166 +15,151 @@ module Twitch
59
15
  end
60
16
  end
61
17
 
18
+ MODERATOR_MESSAGES_COUNT = 100
19
+ USER_MESSAGES_COUNT = 20
20
+ TWITCH_PERIOD = 30.0
21
+
62
22
  attr_reader :connection
63
23
 
64
24
  def initialize(
65
- connection:, output: STDOUT, channel: nil, &block
25
+ connection:, channel: nil, &block
66
26
  )
67
27
  @connection = connection
68
- @logger = Logger.new(output)
69
28
  @channel = Twitch::Bot::Channel.new(channel) if channel
70
29
  @messages_queue = []
71
- @running = false
72
30
  @event_handlers = {}
31
+ @event_loop_running = false
32
+
33
+ create_adapter
73
34
 
74
35
  execute_initialize_block block if block
75
36
  register_default_handlers
76
37
  end
77
38
 
78
- def trigger(event)
79
- type = event.type
80
- logger.debug "Triggered #{type}"
81
- (event_handlers[type] || []).each do |handler_class|
82
- logger.debug "Calling #{handler_class}..."
83
- handler_class.new(event: event, client: self).call
84
- end
85
- end
86
-
87
39
  def register_handler(handler)
88
40
  handler.handled_events.each do |event_type|
89
41
  (event_handlers[event_type] ||= []) << handler
42
+ Twitch::Bot::Logger.debug "Registered #{handler} for #{event_type}"
90
43
  end
91
44
  end
92
45
 
93
46
  def run
94
- raise "Already running" if running
95
-
96
- @running = true
97
-
98
- %w[TERM INT].each { |signal| trap(signal) { stop } }
47
+ startup
99
48
 
100
- connect
49
+ # Wait for threads to finish
101
50
  input_thread.join
102
- messages_thread.join
103
- logger.info "Client ended."
51
+ output_thread.join
104
52
  end
105
53
 
106
- def join(channel)
107
- @channel = Channel.new(channel)
108
- send_data "JOIN ##{@channel.name}"
54
+ def join_default_channel
55
+ adapter.join_channel(@channel) if @channel
109
56
  end
110
57
 
111
- def part
112
- send_data "PART ##{@channel.name}"
58
+ def part_channel
59
+ adapter.part_channel
113
60
  @channel = nil
114
61
  @messages_queue = []
115
62
  end
116
63
 
117
- def send_message(message)
118
- @messages_queue << message if @messages_queue.last != message
64
+ def dispatch(event)
65
+ type = event.type
66
+ Twitch::Bot::Logger.debug "Dispatching #{type}..."
67
+ (event_handlers[type] || []).each do |handler_class|
68
+ Twitch::Bot::Logger.debug "Calling #{handler_class}..."
69
+ handler_class.new(event: event, client: self).call
70
+ end
119
71
  end
120
72
 
121
- def max_messages_count
122
- if @channel.moderators.include?(connection.nickname)
123
- MODERATOR_MESSAGES_COUNT
124
- else
125
- USER_MESSAGES_COUNT
126
- end
73
+ def send_data(data)
74
+ adapter.send_data(data)
127
75
  end
128
76
 
129
- def message_delay
130
- TWITCH_PERIOD / max_messages_count
77
+ def send_message(message)
78
+ messages_queue << message if messages_queue.last != message
131
79
  end
132
80
 
133
81
  def stop
134
- trigger StopEvent.new
135
- @running = false
136
- part if @channel
82
+ dispatch StopEvent.new
83
+ stop_event_loop
84
+ part_channel if channel
137
85
  end
138
86
 
139
- def send_data(data)
140
- log_data = data.gsub(/(PASS oauth:)(\w+)/) do
141
- "#{Regexp.last_match(1)}#{'*' * Regexp.last_match(2).size}"
142
- end
143
- logger.debug "< #{log_data}"
87
+ private
144
88
 
145
- socket.puts(data)
146
- end
89
+ attr_reader :adapter, :event_handlers, :event_loop_running,
90
+ :input_thread, :output_thread, :channel, :messages_queue
147
91
 
148
- def join_default_channel
149
- join @channel.name if @channel
92
+ def create_adapter
93
+ adapter_class = if development_mode?
94
+ Twitch::Bot::Adapter::Terminal
95
+ else
96
+ Twitch::Bot::Adapter::Irc
97
+ end
98
+ @adapter = adapter_class.new(client: self)
150
99
  end
151
100
 
152
- def add_moderator(user)
153
- channel.add_moderator(user)
101
+ def startup
102
+ set_traps
103
+ start_event_loop
104
+ start_input_thread
105
+ start_output_thread
106
+ adapter.connect
107
+ Twitch::Bot::Logger.debug "Started."
154
108
  end
155
109
 
156
- def remove_moderator(user)
157
- channel.remove_moderator(user)
110
+ def set_traps
111
+ %w[TERM INT].each { |signal| trap(signal) { stop } }
158
112
  end
159
113
 
160
- private
161
-
162
- attr_reader :event_handlers, :running, :input_thread, :messages_thread,
163
- :socket, :logger
164
-
165
- def connect
166
- @socket = ::TCPSocket.new(connection.hostname, connection.port)
114
+ def start_event_loop
115
+ raise "Already running" if event_loop_running?
167
116
 
168
- start_input_thread
169
- start_messages_thread
170
- enable_twitch_capabilities
171
- authenticate
117
+ @event_loop_running = true
172
118
  end
173
119
 
174
- def enable_twitch_capabilities
175
- send_data <<~DATA
176
- CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership
177
- DATA
120
+ def stop_event_loop
121
+ @event_loop_running = false
178
122
  end
179
123
 
180
- def authenticate
181
- send_data "PASS #{connection.password}"
182
- send_data "NICK #{connection.nickname}"
124
+ def event_loop_running?
125
+ @event_loop_running
183
126
  end
184
127
 
185
128
  def start_input_thread
129
+ Twitch::Bot::Logger.debug("Starting input thread...")
186
130
  @input_thread = Thread.start do
187
- while running
188
- irc_message = IrcMessage.new(read_socket)
189
- trigger(Twitch::Bot::MessageParser.new(irc_message).message)
131
+ while event_loop_running?
132
+ event = adapter.read_data
133
+ dispatch(event)
190
134
  end
191
-
192
- logger.debug "End of input thread"
193
- socket.close
194
- end
195
- end
196
-
197
- # Acceptable :reek:NilCheck
198
- def read_socket
199
- line = ""
200
- while line.empty?
201
- line = socket.gets&.chomp
202
135
  end
203
- logger.debug "> #{line}"
204
- line
205
136
  end
206
137
 
207
- def start_messages_thread
208
- @messages_thread = Thread.start do
209
- while running
138
+ def start_output_thread
139
+ Twitch::Bot::Logger.debug("Starting output thread...")
140
+ @output_thread = Thread.start do
141
+ while event_loop_running?
210
142
  sleep message_delay
211
143
 
212
- # TODO: Replace with core Queue
213
- if (message = @messages_queue.pop)
214
- send_data "PRIVMSG ##{@channel.name} :#{message}"
144
+ if (message = messages_queue.pop)
145
+ adapter.send_message(message)
215
146
  end
216
147
  end
217
-
218
- logger.debug "End of messages thread"
219
148
  end
220
149
  end
221
150
 
151
+ def development_mode?
152
+ ENV["BOT_MODE"] == "development"
153
+ end
154
+
155
+ def add_moderator(user)
156
+ channel.add_moderator(user)
157
+ end
158
+
159
+ def remove_moderator(user)
160
+ channel.remove_moderator(user)
161
+ end
162
+
222
163
  def execute_initialize_block(block)
223
164
  if block.arity == 1
224
165
  block.call self
@@ -232,6 +173,18 @@ module Twitch
232
173
  register_handler(Twitch::Bot::Client::AuthenticatedHandler)
233
174
  register_handler(Twitch::Bot::Client::ModeHandler)
234
175
  end
176
+
177
+ def max_messages_count
178
+ if channel.moderators.include?(connection.nickname)
179
+ MODERATOR_MESSAGES_COUNT
180
+ else
181
+ USER_MESSAGES_COUNT
182
+ end
183
+ end
184
+
185
+ def message_delay
186
+ TWITCH_PERIOD / max_messages_count
187
+ end
235
188
  end
236
189
  end
237
190
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twitch
4
+ module Bot
5
+ # Twitch chat client object
6
+ class Client
7
+ # Respond to a :ping event with a pong so we don't get disconnected.
8
+ class PingHandler < Twitch::Bot::EventHandler
9
+ def call
10
+ client.send_data "PONG :#{event.hostname}"
11
+ end
12
+
13
+ def self.handled_events
14
+ [:ping]
15
+ end
16
+ end
17
+
18
+ # Handle the :authenticated event required for joining our channel.
19
+ class AuthenticatedHandler < Twitch::Bot::EventHandler
20
+ def call
21
+ client.join_default_channel
22
+ end
23
+
24
+ def self.handled_events
25
+ [:authenticated]
26
+ end
27
+ end
28
+
29
+ # Handle a change in moderators on the channel.
30
+ class ModeHandler < Twitch::Bot::EventHandler
31
+ def call
32
+ user = event.user
33
+ case event.mode
34
+ when :add_moderator
35
+ client.add_moderator(user)
36
+ when :remove_moderator
37
+ client.remove_moderator(user)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twitch
4
+ module Bot
5
+ class Logger
6
+ def self.output=(file)
7
+ @logger = ::Logger.new(file)
8
+ end
9
+
10
+ def self.level=(level)
11
+ @logger.level = level
12
+ end
13
+
14
+ def self.debug(entry)
15
+ logger.debug entry
16
+ end
17
+
18
+ def self.logger
19
+ @logger ||= ::Logger.new(STDOUT)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -22,7 +22,7 @@ module Twitch
22
22
  end
23
23
 
24
24
  def command
25
- first_word.match(/^!(\w+)/) do |match|
25
+ first_word&.match(/^!(\w+)/) do |match|
26
26
  match.captures&.first
27
27
  end
28
28
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Twitch
4
4
  module Bot
5
- VERSION = "2.0.1"
5
+ VERSION = "2.1.0"
6
6
  end
7
7
  end
data/lib/twitch/bot.rb CHANGED
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "bot/version"
4
+ require_relative "bot/logger"
4
5
  require_relative "bot/event"
5
6
  require_relative "bot/event_handler"
7
+ require_relative "bot/default_handlers"
6
8
  require_relative "bot/message"
7
9
  require_relative "bot/irc_message"
8
10
  require_relative "bot/message_parser"
9
11
  require_relative "bot/channel"
10
12
  require_relative "bot/connection"
13
+ require_relative "bot/adapter/irc"
14
+ require_relative "bot/adapter/terminal"
11
15
  require_relative "bot/client"
@@ -14,7 +14,7 @@ RSpec.describe Twitch::Bot::Client do
14
14
  message = ping_message_fake.new(:ping, "test.twitch", nil)
15
15
  allow(client).to receive(:send_data)
16
16
 
17
- client.trigger(message)
17
+ client.dispatch(message)
18
18
 
19
19
  expect(client).to have_received(:send_data)
20
20
  end
@@ -24,7 +24,7 @@ RSpec.describe Twitch::Bot::Client do
24
24
  message = fake_message_class.new(:authenticated)
25
25
  allow(client).to receive(:join_default_channel)
26
26
 
27
- client.trigger(message)
27
+ client.dispatch(message)
28
28
 
29
29
  expect(client).to have_received(:join_default_channel)
30
30
  end
@@ -34,7 +34,7 @@ RSpec.describe Twitch::Bot::Client do
34
34
  message = fake_message_class.new(:mode, "Test", :add_moderator)
35
35
  allow(client).to receive(:add_moderator)
36
36
 
37
- client.trigger(message)
37
+ client.dispatch(message)
38
38
 
39
39
  expect(client).to have_received(:add_moderator)
40
40
  end
@@ -44,7 +44,7 @@ RSpec.describe Twitch::Bot::Client do
44
44
  message = fake_message_class.new(:mode, "Test", :remove_moderator)
45
45
  allow(client).to receive(:remove_moderator)
46
46
 
47
- client.trigger(message)
47
+ client.dispatch(message)
48
48
 
49
49
  expect(client).to have_received(:remove_moderator)
50
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twitch-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jochen Lillich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-16 00:00:00.000000000 Z
11
+ date: 2020-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -187,12 +187,16 @@ files:
187
187
  - README.md
188
188
  - Rakefile
189
189
  - lib/twitch/bot.rb
190
+ - lib/twitch/bot/adapter/irc.rb
191
+ - lib/twitch/bot/adapter/terminal.rb
190
192
  - lib/twitch/bot/channel.rb
191
193
  - lib/twitch/bot/client.rb
192
194
  - lib/twitch/bot/connection.rb
195
+ - lib/twitch/bot/default_handlers.rb
193
196
  - lib/twitch/bot/event.rb
194
197
  - lib/twitch/bot/event_handler.rb
195
198
  - lib/twitch/bot/irc_message.rb
199
+ - lib/twitch/bot/logger.rb
196
200
  - lib/twitch/bot/message.rb
197
201
  - lib/twitch/bot/message/user_message.rb
198
202
  - lib/twitch/bot/message_parser.rb