twitch-bot 1.0.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 +7 -0
- data/.editorconfig +13 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/.vscode/launch.json +28 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +14 -0
- data/lib/twitch/bot/channel.rb +23 -0
- data/lib/twitch/bot/client.rb +238 -0
- data/lib/twitch/bot/connection.rb +19 -0
- data/lib/twitch/bot/event.rb +14 -0
- data/lib/twitch/bot/event_handler.rb +33 -0
- data/lib/twitch/bot/irc_message.rb +143 -0
- data/lib/twitch/bot/message_parser.rb +121 -0
- data/lib/twitch/bot/twitch_message.rb +147 -0
- data/lib/twitch/bot/version.rb +7 -0
- data/lib/twitch/bot.rb +11 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/twitch/bot/client_spec.rb +52 -0
- data/spec/twitch/bot/message_parser_spec.rb +133 -0
- data/twitch-bot.gemspec +36 -0
- metadata +229 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twitch
|
4
|
+
module Bot
|
5
|
+
# This class parses the tags portion in an IRC message.
|
6
|
+
# rubocop:disable Layout/LineLength
|
7
|
+
# <message> ::= ['@' <tags> <SPACE>] [':' <prefix> <SPACE> ] <command> <params> <crlf>
|
8
|
+
# <tags> ::= <tag> [';' <tag>]*
|
9
|
+
# <tag> ::= <key> ['=' <escaped_value>]
|
10
|
+
# <key> ::= [ <client_prefix> ] [ <vendor> '/' ] <key_name>
|
11
|
+
# <client_prefix> ::= '+'
|
12
|
+
# <key_name> ::= <non-empty sequence of ascii letters, digits, hyphens ('-')>
|
13
|
+
# <escaped_value> ::= <sequence of zero or more utf8 characters except NUL, CR, LF, semicolon (`;`) and SPACE>
|
14
|
+
# <vendor> ::= <host>
|
15
|
+
# rubocop:enable Layout/LineLength
|
16
|
+
class IrcMessageTags
|
17
|
+
attr_reader :tags
|
18
|
+
|
19
|
+
def initialize(raw_tags)
|
20
|
+
@raw_tags = raw_tags
|
21
|
+
@tags = parse
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@tags[key]
|
26
|
+
end
|
27
|
+
|
28
|
+
def include?(key)
|
29
|
+
@tags.key?(key)
|
30
|
+
end
|
31
|
+
|
32
|
+
def numeric_state(key, name, off_value:)
|
33
|
+
return unless tags.key?(key)
|
34
|
+
|
35
|
+
case tags[key]
|
36
|
+
when off_value
|
37
|
+
"#{name}_off".to_sym
|
38
|
+
else
|
39
|
+
name.to_sym
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def boolean_state(key, name)
|
44
|
+
return unless tags.key?(key)
|
45
|
+
|
46
|
+
case tags[key]
|
47
|
+
when "1"
|
48
|
+
name
|
49
|
+
when "0"
|
50
|
+
"#{name}_off".to_sym
|
51
|
+
else
|
52
|
+
raise "Unsupported value of '#{key}'"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :raw_tags
|
59
|
+
|
60
|
+
def parse
|
61
|
+
return unless raw_tags
|
62
|
+
|
63
|
+
raw_tags.
|
64
|
+
split(";").
|
65
|
+
map { |key_value| key_value.split("=", 2) }.
|
66
|
+
to_h
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# This class parses the params portion of an IRC message.
|
71
|
+
class IrcMessageParams
|
72
|
+
attr_reader :params
|
73
|
+
|
74
|
+
def initialize(raw_params)
|
75
|
+
@raw_params = raw_params.strip
|
76
|
+
@params = parse
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :raw_params
|
82
|
+
|
83
|
+
def parse
|
84
|
+
if (match = raw_params.match(/(?:^:| :)(.*)$/))
|
85
|
+
params = match.pre_match.split(" ")
|
86
|
+
params << match[1]
|
87
|
+
else
|
88
|
+
raw_params.split(" ")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# This class splits an IRC message into its basic parts.
|
94
|
+
# see https://ircv3.net/specs/extensions/message-tags.html#format
|
95
|
+
#
|
96
|
+
# rubocop:disable Layout/LineLength
|
97
|
+
# <message> ::= ['@' <tags> <SPACE>] [':' <prefix> <SPACE> ] <command> <params> <crlf>
|
98
|
+
# rubocop:enable Layout/LineLength
|
99
|
+
class IrcMessage
|
100
|
+
attr_reader :tags, :prefix, :command, :params
|
101
|
+
|
102
|
+
def initialize(msg)
|
103
|
+
raw_tags, @prefix, @command, raw_params = msg.match(
|
104
|
+
/^(?:@(\S+) )?(?::(\S+) )?(\S+)(.*)/,
|
105
|
+
).captures
|
106
|
+
|
107
|
+
@tags = IrcMessageTags.new(raw_tags)
|
108
|
+
@params = IrcMessageParams.new(raw_params).params
|
109
|
+
end
|
110
|
+
|
111
|
+
def error
|
112
|
+
command[/[45]\d\d/] ? command.to_i : 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def error?
|
116
|
+
error.positive?
|
117
|
+
end
|
118
|
+
|
119
|
+
def target
|
120
|
+
channel || user
|
121
|
+
end
|
122
|
+
|
123
|
+
def text
|
124
|
+
if error?
|
125
|
+
error.to_s
|
126
|
+
else
|
127
|
+
params.last
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def user
|
132
|
+
return unless prefix
|
133
|
+
|
134
|
+
prefix[/^(\S+)!/, 1]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Not really a :reek:NilCheck
|
138
|
+
def channel
|
139
|
+
params.detect { |param| param.start_with?("#") }&.delete("#")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twitch
|
4
|
+
module Bot
|
5
|
+
# This class calls the parser related to the IRC command we received.
|
6
|
+
class MessageParser
|
7
|
+
def initialize(irc_message)
|
8
|
+
@irc_message = irc_message
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
parse_command
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :irc_message
|
18
|
+
|
19
|
+
def parse_command
|
20
|
+
command_parser = {
|
21
|
+
"MODE" => ModeCommandParser,
|
22
|
+
"PING" => PingCommandParser,
|
23
|
+
"372" => AuthenticatedCommandParser,
|
24
|
+
"366" => JoinCommandParser,
|
25
|
+
"PRIVMSG" => PrivMsgCommandParser,
|
26
|
+
"ROOMSTATE" => RoomStateCommandParser,
|
27
|
+
"NOTICE" => NoticeCommandParser,
|
28
|
+
}
|
29
|
+
parser = command_parser[irc_message.command]
|
30
|
+
if parser
|
31
|
+
parser.new(irc_message).call
|
32
|
+
else
|
33
|
+
NotSupportedMessage.new(irc_message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Abstract base class for a parser for a specific IRC command.
|
39
|
+
class CommandParser
|
40
|
+
attr_reader :message
|
41
|
+
|
42
|
+
def initialize(message)
|
43
|
+
@message = message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parses a PING IRC command
|
48
|
+
class PingCommandParser < CommandParser
|
49
|
+
def call
|
50
|
+
PingMessage.new(message)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Parses a PRIVMSG IRC command
|
55
|
+
class PrivMsgCommandParser < CommandParser
|
56
|
+
def call
|
57
|
+
if message.user == "twitchnotify"
|
58
|
+
if message.text.match?(/just subscribed!/)
|
59
|
+
SubscriptionMessage.new(message)
|
60
|
+
else
|
61
|
+
NotSupportedMessage.new(message)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
ChatMessageMessage.new(message)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parses a MODE IRC command
|
70
|
+
class ModeCommandParser < CommandParser
|
71
|
+
def call
|
72
|
+
ModeMessage.new(message)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Parses a 372 IRC status code/command.
|
77
|
+
class AuthenticatedCommandParser < CommandParser
|
78
|
+
def call
|
79
|
+
AuthenticatedMessage.new(message)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Parses a 366 IRC status code/command.
|
84
|
+
class JoinCommandParser < CommandParser
|
85
|
+
def call
|
86
|
+
JoinMessage.new(message)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Parses a ROOMSTATE IRC command.
|
91
|
+
class RoomStateCommandParser < CommandParser
|
92
|
+
def call
|
93
|
+
roomstate_tags = {
|
94
|
+
"slow" => SlowModeMessage,
|
95
|
+
"followers-only" => FollowersOnlyModeMessage,
|
96
|
+
"subs-only" => SubsOnlyModeMessage,
|
97
|
+
"r9k" => R9kModeMessage,
|
98
|
+
}
|
99
|
+
|
100
|
+
roomstate_tags.each do |tag, event|
|
101
|
+
if message.tags.include?(tag)
|
102
|
+
return event.new(message)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
NotSupportedMessage.new(message)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parses a NOTICE IRC command.
|
111
|
+
class NoticeCommandParser < CommandParser
|
112
|
+
def call
|
113
|
+
if message.params.last.match?(/Login authentication failed/)
|
114
|
+
LoginFailedMessage.new(message)
|
115
|
+
else
|
116
|
+
NotSupportedMessage.new(message)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,147 @@
|
|
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
|
data/lib/twitch/bot.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "bot/version"
|
4
|
+
require_relative "bot/event"
|
5
|
+
require_relative "bot/event_handler"
|
6
|
+
require_relative "bot/twitch_message"
|
7
|
+
require_relative "bot/irc_message"
|
8
|
+
require_relative "bot/message_parser"
|
9
|
+
require_relative "bot/channel"
|
10
|
+
require_relative "bot/connection"
|
11
|
+
require_relative "bot/client"
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Twitch::Bot::Client do
|
4
|
+
let!(:client) do
|
5
|
+
connection = Twitch::Bot::Connection.new(
|
6
|
+
nickname: "test", password: "test",
|
7
|
+
)
|
8
|
+
described_class.new(connection: connection)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#trigger" do
|
12
|
+
it "responds to a Ping message" do
|
13
|
+
ping_message_fake = Struct.new(:type, :hostname, :user)
|
14
|
+
message = ping_message_fake.new(:ping, "test.twitch", nil)
|
15
|
+
allow(client).to receive(:send_data)
|
16
|
+
|
17
|
+
client.trigger(message)
|
18
|
+
|
19
|
+
expect(client).to have_received(:send_data)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "responds to an Authenticate message" do
|
23
|
+
fake_message_class = Struct.new(:type)
|
24
|
+
message = fake_message_class.new(:authenticated)
|
25
|
+
allow(client).to receive(:join_default_channel)
|
26
|
+
|
27
|
+
client.trigger(message)
|
28
|
+
|
29
|
+
expect(client).to have_received(:join_default_channel)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "responds to a +o message" do
|
33
|
+
fake_message_class = Struct.new(:type, :user, :mode)
|
34
|
+
message = fake_message_class.new(:mode, "Test", :add_moderator)
|
35
|
+
allow(client).to receive(:add_moderator)
|
36
|
+
|
37
|
+
client.trigger(message)
|
38
|
+
|
39
|
+
expect(client).to have_received(:add_moderator)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "responds to a -o message" do
|
43
|
+
fake_message_class = Struct.new(:type, :user, :mode)
|
44
|
+
message = fake_message_class.new(:mode, "Test", :remove_moderator)
|
45
|
+
allow(client).to receive(:remove_moderator)
|
46
|
+
|
47
|
+
client.trigger(message)
|
48
|
+
|
49
|
+
expect(client).to have_received(:remove_moderator)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Twitch::Bot::MessageParser do
|
4
|
+
context "when we receive a PRIVMSG message" do
|
5
|
+
it "parses a chat message event" do
|
6
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
7
|
+
:enotpoloskun!enotpoloskun@enotpoloskun.tmi.twitch.tv PRIVMSG #enotpoloskun :BibleThump
|
8
|
+
RAW
|
9
|
+
|
10
|
+
event = described_class.new(irc_message).message
|
11
|
+
|
12
|
+
expect(event.type).to eq :chat_message
|
13
|
+
expect(event.text).to eq "BibleThump"
|
14
|
+
expect(event.user).to eq "enotpoloskun"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "parses a slow_mode enable event" do
|
18
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
19
|
+
@room-id=117474239;slow=10 :tmi.twitch.tv ROOMSTATE #alexwayfer
|
20
|
+
RAW
|
21
|
+
|
22
|
+
event = described_class.new(irc_message).message
|
23
|
+
|
24
|
+
expect(event.type).to eq :slow_mode
|
25
|
+
expect(event.status).to eq "10"
|
26
|
+
expect(event.channel).to eq "alexwayfer"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "parses a slow mode disable event" do
|
30
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
31
|
+
@room-id=117474239;slow=0 :tmi.twitch.tv ROOMSTATE #alexwayfer
|
32
|
+
RAW
|
33
|
+
|
34
|
+
event = described_class.new(irc_message).message
|
35
|
+
|
36
|
+
expect(event.type).to eq :slow_mode
|
37
|
+
expect(event.status).to eq "0"
|
38
|
+
expect(event.channel).to eq "alexwayfer"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "parses an r9k mode enable event" do
|
42
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
43
|
+
@r9k=1;room-id=117474239 :tmi.twitch.tv ROOMSTATE #alexwayfer
|
44
|
+
RAW
|
45
|
+
|
46
|
+
event = described_class.new(irc_message).message
|
47
|
+
|
48
|
+
expect(event.type).to eq :r9k_mode
|
49
|
+
expect(event.status).to eq "1"
|
50
|
+
expect(event.channel).to eq "alexwayfer"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "parses an r9k mode disable event" do
|
54
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
55
|
+
@r9k=0;room-id=117474239 :tmi.twitch.tv ROOMSTATE #alexwayfer
|
56
|
+
RAW
|
57
|
+
|
58
|
+
event = described_class.new(irc_message).message
|
59
|
+
|
60
|
+
expect(event.type).to eq :r9k_mode
|
61
|
+
expect(event.status).to eq "0"
|
62
|
+
expect(event.channel).to eq "alexwayfer"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "parses a subs-only mode enable event" do
|
66
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
67
|
+
@room-id=128644134;subs-only=1 :tmi.twitch.tv ROOMSTATE #sad_satont
|
68
|
+
RAW
|
69
|
+
|
70
|
+
event = described_class.new(irc_message).message
|
71
|
+
|
72
|
+
expect(event.type).to eq :subs_only_mode
|
73
|
+
expect(event.channel).to eq "sad_satont"
|
74
|
+
end
|
75
|
+
|
76
|
+
it "parses a subs-only mode disable event" do
|
77
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
78
|
+
@room-id=128644134;subs-only=0 :tmi.twitch.tv ROOMSTATE #sad_satont
|
79
|
+
RAW
|
80
|
+
|
81
|
+
event = described_class.new(irc_message).message
|
82
|
+
|
83
|
+
expect(event.type).to eq :subs_only_mode
|
84
|
+
expect(event.channel).to eq "sad_satont"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "handles a subscription event" do
|
88
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
89
|
+
:twitchnotify!twitchnotify@twitchnotify.tmi.twitch.tv PRIVMSG #enotpoloskun :enotpoloskun just subscribed!
|
90
|
+
RAW
|
91
|
+
|
92
|
+
event = described_class.new(irc_message).message
|
93
|
+
|
94
|
+
expect(event.type).to eq :subscription
|
95
|
+
expect(event.user).to eq "enotpoloskun"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "handles a MODE event" do
|
100
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
101
|
+
:jtv MODE #enotpoloskun +o enotpoloskun
|
102
|
+
RAW
|
103
|
+
|
104
|
+
event = described_class.new(irc_message).message
|
105
|
+
|
106
|
+
expect(event.user).to eq "enotpoloskun"
|
107
|
+
expect(event.type).to eq :mode
|
108
|
+
end
|
109
|
+
|
110
|
+
it "parses a PING event" do
|
111
|
+
host = "tmi.twitch.tv"
|
112
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
113
|
+
PING :#{host}
|
114
|
+
RAW
|
115
|
+
|
116
|
+
event = described_class.new(irc_message).message
|
117
|
+
|
118
|
+
expect(event.user).to eq nil
|
119
|
+
expect(event.type).to eq :ping
|
120
|
+
expect(event.hostname).to eq host
|
121
|
+
end
|
122
|
+
|
123
|
+
it "parses a NOTIFY event" do
|
124
|
+
irc_message = Twitch::Bot::IrcMessage.new(<<~RAW)
|
125
|
+
:tmi.twitch.tv NOTICE * :Login authentication failed
|
126
|
+
RAW
|
127
|
+
|
128
|
+
event = described_class.new(irc_message).message
|
129
|
+
|
130
|
+
expect(event.user).to eq nil
|
131
|
+
expect(event.type).to eq :login_failed
|
132
|
+
end
|
133
|
+
end
|
data/twitch-bot.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/twitch/bot/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "twitch-bot"
|
7
|
+
spec.version = Twitch::Bot::VERSION
|
8
|
+
spec.authors = ["Jochen Lillich"]
|
9
|
+
spec.email = ["contact@geewiz.dev"]
|
10
|
+
spec.summary = <<~SUMMARY
|
11
|
+
twitch-bot is a Twitch chat client that uses Twitch IRC that can be used as a Twitch chat bot engine.
|
12
|
+
SUMMARY
|
13
|
+
spec.description = <<~DESC
|
14
|
+
twitch-bot is a Twitch chat client that uses Twitch IRC.
|
15
|
+
With the help of this library you can connect to any Twitch channel and handle chat events.
|
16
|
+
DESC
|
17
|
+
spec.homepage = "https://github.com/geewiz/twitch-bot"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
26
|
+
spec.add_development_dependency "freistil-rubocop"
|
27
|
+
spec.add_development_dependency "guard", "~> 2.16"
|
28
|
+
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
29
|
+
spec.add_development_dependency "pry-byebug", "~> 3.0"
|
30
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
31
|
+
spec.add_development_dependency "reek"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
spec.add_development_dependency "rubocop"
|
34
|
+
spec.add_development_dependency "solargraph"
|
35
|
+
spec.add_development_dependency "terminal-notifier-guard"
|
36
|
+
end
|