twitch-bot 1.0.0 → 2.0.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: a10dd280ea4e2cf20e4d4066f119b13622aae30e7d03f9bd3c6407113b05e5b8
4
- data.tar.gz: 58c80ab7b5c89b67e317986713e854d4797048bca8d5cde5c11da2966022a54f
3
+ metadata.gz: b2e0942b8b6cf90e70cfec766be2664cf708f33b849bb2b59c5cc546f202e947
4
+ data.tar.gz: 54af782c020710290308fe279e10d52dd5bd6dc088254ae997c257854c53f9e2
5
5
  SHA512:
6
- metadata.gz: c8c987740eff97e5b02e8b0b3a9c996d43948de76cedfe3ba83464c9115f025b81691f0de167cdaa91a82a4499d5c1a88ac5d926d1260f7a76b7ea280b7b9d02
7
- data.tar.gz: 00a30a330696adbbf506fc4858b52a8e1cb4670ad56a49606433ad05d185868f62bed7ca4e09b0758a40d8817cd513ce59c10cd099e979b64fbf04209eb707b4
6
+ metadata.gz: 833f91e3d01dd7b2c5fd894e026c5a62155350146df9d7e80172fc68c99961f7e6986cb4b03f53e76785d53ddbcb70dcd414b1224d03d8b50a56b0fb525fcae2
7
+ data.tar.gz: 5e8bae84a2ab043e4aabb70206bb93eaf163e4bdd7711c7dde4d3977bd4100ddcff2bba905fcd68ca7ed81f5976a3346e8b42372b0c6a40f07cf31d965325bb8
data/.gitignore CHANGED
@@ -13,3 +13,6 @@
13
13
  *.o
14
14
  *.a
15
15
  mkmf.log
16
+
17
+ # rspec failure tracking
18
+ .rspec_status
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --require spec_helper
3
+ --format doc
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog Twitch::Bot
2
+
3
+ ## v2.0.0
4
+
5
+ * [BREAKING] The standard text message class is now named
6
+ `Twitch::Bot::Message::UserMessage` and uses the type symbol `:user_message`. It has added methods to handle bot commands.
7
+ * [CHANGED] Major restructuring of both class hierarchy and class responsibilities.
8
+
9
+ ## v1.0.0 Initial release
10
+
11
+ This is the first release of `Twitch::Bot`, a fork and evolution of the `Twitch::Chat` gem. Its goal is to become a cleanly designed framework for building Twitch chat bots.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Pavel Astraukh
1
+ Copyright (c) 2014 Pavel Astraukh, 2020 Jochen Lillich
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  This gem is based on the `twitch-chat` gem by https://github.com/EnotPoloskun.
6
6
 
7
+ _As of April 2020, this gem is under heavy development. I've created it to develop my own Twitch chat bot and will add more functionality. Feel free to add issues with suggestions and feature requests!_
8
+
7
9
  ## Installation
8
10
 
9
11
  Add this line to your application's `Gemfile`:
@@ -140,8 +140,8 @@ module Twitch
140
140
  log_data = data.gsub(/(PASS oauth:)(\w+)/) do
141
141
  "#{Regexp.last_match(1)}#{'*' * Regexp.last_match(2).size}"
142
142
  end
143
+ logger.debug "< #{log_data}"
143
144
 
144
- logger.info "< #{log_data}"
145
145
  socket.puts(data)
146
146
  end
147
147
 
@@ -186,7 +186,7 @@ module Twitch
186
186
  @input_thread = Thread.start do
187
187
  while running
188
188
  line = read_socket
189
- logger.info "> #{line}"
189
+ logger.debug "> #{line}"
190
190
  irc_message = IrcMessage.new(line)
191
191
  trigger(Twitch::Bot::MessageParser.new(irc_message).message)
192
192
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twitch
4
+ module Bot
5
+ module Message
6
+ # This class stores the details of a user's chat message.
7
+ class UserMessage < Base
8
+ attr_reader :text, :user
9
+
10
+ def initialize(text:, user:)
11
+ @text = text
12
+ @user = user
13
+ @type = :user_message
14
+ end
15
+
16
+ def command_name?(check_command)
17
+ command == check_command
18
+ end
19
+
20
+ def command?
21
+ !(command.nil? || command.empty?)
22
+ end
23
+
24
+ def command
25
+ first_word.match(/^!(\w+)/) do |match|
26
+ match.captures&.first
27
+ end
28
+ end
29
+
30
+ def command_args
31
+ text_words.tap(&:shift)
32
+ end
33
+
34
+ private
35
+
36
+ def text_words
37
+ text.split(/\s+/)
38
+ end
39
+
40
+ def first_word
41
+ text_words.first
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twitch
4
+ module Bot
5
+ module Message
6
+ # This class is the abstract base class for IRC events.
7
+ class Base < Twitch::Bot::Event
8
+ def initialize(_message)
9
+ @type = :unknown
10
+ end
11
+ end
12
+
13
+ # This class represents an event that is not supported.
14
+ class NotSupported < Base
15
+ attr_reader :message
16
+
17
+ def initialize(message)
18
+ @message = message
19
+ @type = :not_supported
20
+ end
21
+ end
22
+
23
+ # This class stores the details of a Ping event.
24
+ class Ping < Base
25
+ attr_reader :hostname
26
+
27
+ def initialize(hostname:)
28
+ @hostname = hostname
29
+ @type = :ping
30
+ end
31
+ end
32
+
33
+ # This class stores the details of a Mode event.
34
+ class Mode < Base
35
+ attr_reader :user, :mode
36
+
37
+ def initialize(user:, mode:)
38
+ @user = user
39
+ @mode = mode
40
+ @type = :mode
41
+ end
42
+ end
43
+
44
+ # This class stores the details of an Authenticated event.
45
+ class Authenticated < Base
46
+ def initialize
47
+ @type = :authenticated
48
+ end
49
+ end
50
+
51
+ # This class stores the details of a Join event.
52
+ class Join < Base
53
+ def initialize
54
+ @type = :join
55
+ end
56
+ end
57
+
58
+ # This class stores the details of a Subscription event.
59
+ class Subscription < Base
60
+ attr_reader :user
61
+
62
+ def initialize(user:)
63
+ @user = user
64
+ @type = :subscription
65
+ end
66
+ end
67
+
68
+ # This class stores the details of a LoginFailed event.
69
+ class LoginFailed < Base
70
+ attr_reader :user
71
+
72
+ def initialize(user:)
73
+ @user = user
74
+ @type = :login_failed
75
+ end
76
+ end
77
+
78
+ # This class stores the details of a SlowMode event.
79
+ class SlowMode < Base
80
+ attr_reader :status, :channel
81
+
82
+ def initialize(status:, channel:)
83
+ @status = status
84
+ @channel = channel
85
+ @type = :slow_mode
86
+ end
87
+
88
+ def enabled?
89
+ status.to_i.positive?
90
+ end
91
+ end
92
+
93
+ # This class stores the details of a FollowersOnlyMode event.
94
+ class FollowersOnlyMode < Base
95
+ attr_reader :status
96
+
97
+ def initialize(status:)
98
+ @status = status
99
+ @type = :followers_only_mode
100
+ end
101
+ end
102
+
103
+ # This class stores the details of a SubsOnlyMode event.
104
+ class SubsOnlyMode < Base
105
+ attr_reader :status, :channel
106
+
107
+ def initialize(status:, channel:)
108
+ @status = status
109
+ @channel = channel
110
+ @type = :subs_only_mode
111
+ end
112
+ end
113
+
114
+ # This class stores the details of a R9kMode event.
115
+ class R9kMode < Base
116
+ attr_reader :status, :channel
117
+
118
+ def initialize(status:, channel:)
119
+ @status = status
120
+ @channel = channel
121
+ @type = :r9k_mode
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ require_relative "message/user_message"
@@ -30,7 +30,7 @@ module Twitch
30
30
  if parser
31
31
  parser.new(irc_message).call
32
32
  else
33
- NotSupportedMessage.new(irc_message)
33
+ Message::NotSupported.new(irc_message)
34
34
  end
35
35
  end
36
36
  end
@@ -47,43 +47,53 @@ module Twitch
47
47
  # Parses a PING IRC command
48
48
  class PingCommandParser < CommandParser
49
49
  def call
50
- PingMessage.new(message)
50
+ Message::Ping.new(hostname: message.params.last)
51
51
  end
52
52
  end
53
53
 
54
54
  # Parses a PRIVMSG IRC command
55
55
  class PrivMsgCommandParser < CommandParser
56
56
  def call
57
- if message.user == "twitchnotify"
58
- if message.text.match?(/just subscribed!/)
59
- SubscriptionMessage.new(message)
57
+ user = message.user
58
+ text = message.text
59
+ if user == "twitchnotify"
60
+ if text.match?(/just subscribed!/)
61
+ Message::Subscription.new(
62
+ user: message.params.last.split(" ").first,
63
+ )
60
64
  else
61
- NotSupportedMessage.new(message)
65
+ Message::NotSupported.new(message)
62
66
  end
63
67
  else
64
- ChatMessageMessage.new(message)
68
+ Message::UserMessage.new(text: text, user: user)
65
69
  end
66
70
  end
67
71
  end
68
72
 
69
73
  # Parses a MODE IRC command
70
74
  class ModeCommandParser < CommandParser
75
+ MODE_CHANGE = {
76
+ "-o" => :remove_moderator,
77
+ "+o" => :add_moderator,
78
+ }.freeze
79
+
71
80
  def call
72
- ModeMessage.new(message)
81
+ params = message.params
82
+ Message::Mode.new(user: params.last, mode: MODE_CHANGE[params[1]])
73
83
  end
74
84
  end
75
85
 
76
86
  # Parses a 372 IRC status code/command.
77
87
  class AuthenticatedCommandParser < CommandParser
78
88
  def call
79
- AuthenticatedMessage.new(message)
89
+ Message::Authenticated.new
80
90
  end
81
91
  end
82
92
 
83
93
  # Parses a 366 IRC status code/command.
84
94
  class JoinCommandParser < CommandParser
85
95
  def call
86
- JoinMessage.new(message)
96
+ Message::Join.new
87
97
  end
88
98
  end
89
99
 
@@ -91,19 +101,54 @@ module Twitch
91
101
  class RoomStateCommandParser < CommandParser
92
102
  def call
93
103
  roomstate_tags = {
94
- "slow" => SlowModeMessage,
95
- "followers-only" => FollowersOnlyModeMessage,
96
- "subs-only" => SubsOnlyModeMessage,
97
- "r9k" => R9kModeMessage,
104
+ "slow" => SlowModeParser,
105
+ "followers-only" => FollowersOnlyModeParser,
106
+ "subs-only" => SubsOnlyModeParser,
107
+ "r9k" => R9kModeParser,
98
108
  }
99
109
 
100
- roomstate_tags.each do |tag, event|
110
+ roomstate_tags.each do |tag, parser|
101
111
  if message.tags.include?(tag)
102
- return event.new(message)
112
+ return parser.new(message).call
103
113
  end
104
114
  end
105
115
 
106
- NotSupportedMessage.new(message)
116
+ Message::NotSupported.new(message)
117
+ end
118
+ end
119
+
120
+ class SlowModeParser < CommandParser
121
+ def call
122
+ Message::SlowMode.new(
123
+ status: message.tags["slow"],
124
+ channel: message.channel,
125
+ )
126
+ end
127
+ end
128
+
129
+ class FollowersOnlyModeParser < CommandParser
130
+ def call
131
+ Message::FollowersOnlyMode.new(
132
+ status: message.tags["followers-only"],
133
+ )
134
+ end
135
+ end
136
+
137
+ class SubsOnlyModeParser < CommandParser
138
+ def call
139
+ Message::SubsOnlyMode.new(
140
+ status: message.tags["subs-only"],
141
+ channel: message.channel,
142
+ )
143
+ end
144
+ end
145
+
146
+ class R9kModeParser < CommandParser
147
+ def call
148
+ Message::R9kMode.new(
149
+ status: message.tags["r9k"],
150
+ channel: message.channel,
151
+ )
107
152
  end
108
153
  end
109
154
 
@@ -111,9 +156,9 @@ module Twitch
111
156
  class NoticeCommandParser < CommandParser
112
157
  def call
113
158
  if message.params.last.match?(/Login authentication failed/)
114
- LoginFailedMessage.new(message)
159
+ Message::LoginFailed.new(user: message.user)
115
160
  else
116
- NotSupportedMessage.new(message)
161
+ Message::NotSupported.new(message)
117
162
  end
118
163
  end
119
164
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Twitch
4
4
  module Bot
5
- VERSION = "1.0.0"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
data/lib/twitch/bot.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require_relative "bot/version"
4
4
  require_relative "bot/event"
5
5
  require_relative "bot/event_handler"
6
- require_relative "bot/twitch_message"
6
+ require_relative "bot/message"
7
7
  require_relative "bot/irc_message"
8
8
  require_relative "bot/message_parser"
9
9
  require_relative "bot/channel"
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler/setup"
4
+ require "rspec/core"
3
5
  require "pry-byebug"
4
6
  require_relative "../lib/twitch/bot"
7
+
8
+ RSpec.configure do |config|
9
+ # Enable flags like --only-failures and --next-failure
10
+ config.example_status_persistence_file_path = ".rspec_status"
11
+
12
+ # Disable RSpec exposing methods globally on `Module` and `main`
13
+ config.disable_monkey_patching!
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Twitch::Bot::Client do
3
+ RSpec.describe Twitch::Bot::Client do
4
4
  let!(:client) do
5
5
  connection = Twitch::Bot::Connection.new(
6
6
  nickname: "test", password: "test",
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Twitch::Bot::Message::UserMessage do
4
+ it "recognizes a bot command" do
5
+ message = described_class.new(
6
+ text: "!command argument1",
7
+ user: "tester",
8
+ )
9
+
10
+ expect(message.command?).to be true
11
+ end
12
+
13
+ it "recognizes a specific bot command" do
14
+ message = described_class.new(
15
+ text: "!command argument1",
16
+ user: "tester",
17
+ )
18
+
19
+ expect(message.command_name?("command")).to be true
20
+ end
21
+
22
+ it "does not recognize normal text as a command" do
23
+ message = described_class.new(
24
+ text: "nocommand here",
25
+ user: "tester",
26
+ )
27
+
28
+ binding.pry
29
+ expect(message.command?).to be false
30
+ end
31
+
32
+ it "does not mistake text for a command" do
33
+ message = described_class.new(
34
+ text: "nocommand here",
35
+ user: "tester",
36
+ )
37
+
38
+ expect(message.command_name?("nocommand")).to be false
39
+ end
40
+
41
+ it "parses a bot command" do
42
+ message = described_class.new(
43
+ text: "!command argument1",
44
+ user: "tester",
45
+ )
46
+
47
+ expect(message.command).to eq "command"
48
+ end
49
+
50
+ it "does not return a command for normal text" do
51
+ message = described_class.new(
52
+ text: "nocommand here",
53
+ user: "tester",
54
+ )
55
+
56
+ expect(message.command).to be nil
57
+ end
58
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- describe Twitch::Bot::MessageParser do
3
+ RSpec.describe Twitch::Bot::MessageParser do
4
4
  context "when we receive a PRIVMSG message" do
5
5
  it "parses a chat message event" do
6
6
  irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
@@ -9,7 +9,7 @@ describe Twitch::Bot::MessageParser do
9
9
 
10
10
  event = described_class.new(irc_message).message
11
11
 
12
- expect(event.type).to eq :chat_message
12
+ expect(event.type).to eq :user_message
13
13
  expect(event.text).to eq "BibleThump"
14
14
  expect(event.user).to eq "enotpoloskun"
15
15
  end
@@ -115,7 +115,6 @@ describe Twitch::Bot::MessageParser do
115
115
 
116
116
  event = described_class.new(irc_message).message
117
117
 
118
- expect(event.user).to eq nil
119
118
  expect(event.type).to eq :ping
120
119
  expect(event.hostname).to eq host
121
120
  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: 1.0.0
4
+ version: 2.0.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-13 00:00:00.000000000 Z
11
+ date: 2020-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -179,6 +179,7 @@ files:
179
179
  - ".rubocop.yml"
180
180
  - ".ruby-version"
181
181
  - ".vscode/launch.json"
182
+ - CHANGELOG.md
182
183
  - CODE_OF_CONDUCT.md
183
184
  - Gemfile
184
185
  - Guardfile
@@ -192,11 +193,13 @@ files:
192
193
  - lib/twitch/bot/event.rb
193
194
  - lib/twitch/bot/event_handler.rb
194
195
  - lib/twitch/bot/irc_message.rb
196
+ - lib/twitch/bot/message.rb
197
+ - lib/twitch/bot/message/user_message.rb
195
198
  - lib/twitch/bot/message_parser.rb
196
- - lib/twitch/bot/twitch_message.rb
197
199
  - lib/twitch/bot/version.rb
198
200
  - spec/spec_helper.rb
199
201
  - spec/twitch/bot/client_spec.rb
202
+ - spec/twitch/bot/message/user_message_spec.rb
200
203
  - spec/twitch/bot/message_parser_spec.rb
201
204
  - twitch-bot.gemspec
202
205
  homepage: https://github.com/geewiz/twitch-bot
@@ -226,4 +229,5 @@ summary: twitch-bot is a Twitch chat client that uses Twitch IRC that can be use
226
229
  test_files:
227
230
  - spec/spec_helper.rb
228
231
  - spec/twitch/bot/client_spec.rb
232
+ - spec/twitch/bot/message/user_message_spec.rb
229
233
  - spec/twitch/bot/message_parser_spec.rb
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # TODO: Use namespace
4
-
5
- module Twitch
6
- module Bot
7
- # This class is the abstract base class for IRC events.
8
- class TwitchMessage < Twitch::Bot::Event
9
- def initialize(_message)
10
- @type = :unknown
11
- end
12
- end
13
-
14
- # This class represents an event that is not supported.
15
- class NotSupportedMessage < TwitchMessage
16
- attr_reader :message
17
-
18
- def initialize(message)
19
- @message = message
20
- @type = :not_supported
21
- end
22
- end
23
-
24
- # This class stores the details of a Ping event.
25
- class PingMessage < TwitchMessage
26
- attr_reader :hostname, :user
27
-
28
- def initialize(message)
29
- @user = message.user
30
- @hostname = message.params.last
31
- @type = :ping
32
- end
33
- end
34
-
35
- # This class stores the details of a Mode event.
36
- class ModeMessage < TwitchMessage
37
- attr_reader :user, :mode
38
-
39
- MODE_CHANGE = {
40
- "+o" => :add_moderator,
41
- "-o" => :remove_moderator,
42
- }.freeze
43
- def initialize(message)
44
- params = message.params
45
- @user = params.last
46
- @mode = MODE_CHANGE[params[1]]
47
- @type = :mode
48
- end
49
- end
50
-
51
- # This class stores the details of an Authenticated event.
52
- class AuthenticatedMessage < TwitchMessage
53
- def initialize(_message)
54
- @type = :authenticated
55
- end
56
- end
57
-
58
- # This class stores the details of a Join event.
59
- class JoinMessage < TwitchMessage
60
- def initialize(_message)
61
- @type = :join
62
- end
63
- end
64
-
65
- # This class stores the details of a Subscription event.
66
- class SubscriptionMessage < TwitchMessage
67
- attr_reader :user
68
-
69
- def initialize(message)
70
- @user = message.params.last.split(" ").first
71
- @type = :subscription
72
- end
73
- end
74
-
75
- # This class stores the details of a ChatMessage event.
76
- class ChatMessageMessage < TwitchMessage
77
- attr_reader :text, :user
78
-
79
- def initialize(message)
80
- @text = message.text
81
- @user = message.user
82
- @type = :chat_message
83
- end
84
-
85
- def bot_command?(command)
86
- text.split(/\s+/).first.match?(/^!#{command}/)
87
- end
88
- end
89
-
90
- # This class stores the details of a LoginFailed event.
91
- class LoginFailedMessage < TwitchMessage
92
- attr_reader :user
93
-
94
- def initialize(message)
95
- @user = message.user
96
- @type = :login_failed
97
- end
98
- end
99
-
100
- # This class stores the details of a SlowMode event.
101
- class SlowModeMessage < TwitchMessage
102
- attr_reader :status, :channel
103
-
104
- def initialize(message)
105
- @status = message.tags["slow"]
106
- @channel = message.channel
107
- @type = :slow_mode
108
- end
109
-
110
- def enabled?
111
- status.to_i.positive?
112
- end
113
- end
114
-
115
- # This class stores the details of a FollowersOnlyMode event.
116
- class FollowersOnlyModeMessage < TwitchMessage
117
- attr_reader :status
118
-
119
- def initialize(message)
120
- @status = message.tags["followers-only"]
121
- @type = :followers_only_mode
122
- end
123
- end
124
-
125
- # This class stores the details of a SubsOnlyMode event.
126
- class SubsOnlyModeMessage < TwitchMessage
127
- attr_reader :status, :channel
128
-
129
- def initialize(message)
130
- @status = message.tags["subs-only"]
131
- @channel = message.channel
132
- @type = :subs_only_mode
133
- end
134
- end
135
-
136
- # This class stores the details of a R9kMode event.
137
- class R9kModeMessage < TwitchMessage
138
- attr_reader :status, :channel
139
-
140
- def initialize(message)
141
- @status = message.tags["r9k"]
142
- @channel = message.channel
143
- @type = :r9k_mode
144
- end
145
- end
146
- end
147
- end