twitch-bot 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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