nakiircbot 0.2.0 → 1.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 +4 -4
- data/lib/nakiircbot.rb +242 -88
- data/nakiircbot.gemspec +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c070c5f505213c2ca011f3a87911603f2b327c2787406c5ffe2b9e8fdc60495
|
4
|
+
data.tar.gz: 145078acb492b0c2afb570be54145eeaca0fa749978b1dcbce59470ac7f84a74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4277a56a3a649aca36bf058cb89bbf6a2ac17de4c9388ae22d341d2de234239d4252d7335c422f41bab6b5fc4e954491900660d4b851eb22040f50de604deed9
|
7
|
+
data.tar.gz: c62d1a88df3c5b58aa3bfce9f0f0b52a7804ec85ca32c3e9e82ebd5dd0ff8fa61412a23553213872a497df505d4c8f666736b5f4fe2332f9193a74c6e8e79852
|
data/lib/nakiircbot.rb
CHANGED
@@ -1,66 +1,104 @@
|
|
1
1
|
module NakiIRCBot
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
Reconnect = ::Class.new ::RuntimeError
|
5
|
+
Queue = ::Queue.new
|
6
6
|
def self.start server, port, bot_name, master_name, welcome001, *channels, identity: nil, password: nil, masterword: nil, processors: [], tags: false
|
7
7
|
# @@channels.replace channels.dup
|
8
8
|
|
9
9
|
abort "matching bot_name and master_name may cause infinite recursion" if bot_name == master_name
|
10
|
-
require "base64"
|
11
10
|
require "fileutils"
|
12
|
-
FileUtils.mkdir_p "logs"
|
11
|
+
::FileUtils.mkdir_p "logs"
|
13
12
|
require "logger"
|
14
|
-
|
15
|
-
|
13
|
+
# TODO: check how I've implemented the logger in trovobot
|
14
|
+
original_formatter = ::Logger::Formatter.new
|
15
|
+
logger = ::Logger.new "logs/txt", "daily",
|
16
16
|
progname: bot_name, datetime_format: "%y%m%d %H%M%S",
|
17
17
|
formatter: lambda{ |severity, datetime, progname, msg|
|
18
18
|
puts "#{datetime.strftime "%H%M%S"} #{severity.to_s[0]} #{progname} #{msg.scrub.inspect[1..-2]}"
|
19
|
-
original_formatter.call severity, datetime, progname, Base64.strict_encode64(msg)
|
19
|
+
original_formatter.call severity, datetime, progname, ::Base64.strict_encode64(msg)
|
20
20
|
# TODO: maybe encode the whole string for a case of invalid progname?
|
21
21
|
}
|
22
|
-
logger.level =
|
22
|
+
logger.level = ::Logger::WARN
|
23
|
+
logger.level = ::ENV["LOGLEVEL_#{name}"].to_sym if ::ENV.include? "LOGLEVEL_#{name}"
|
23
24
|
puts "#{name} logger.level = #{logger.level}"
|
24
25
|
|
25
|
-
# https://
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
# https://stackoverflow.com/a/49476047/322020
|
27
|
+
|
28
|
+
require "socket"
|
29
|
+
socket = ::Module.new do
|
30
|
+
@logger = logger
|
31
|
+
@server = server
|
32
|
+
@port = port
|
33
|
+
|
34
|
+
@socket = nil
|
35
|
+
@mutex = ::Mutex.new
|
36
|
+
def self.socket
|
37
|
+
reconnect = lambda do
|
38
|
+
@logger.warn "socket: reconnecting"
|
39
|
+
begin
|
40
|
+
@socket = ::TCPSocket.new @server, @port
|
41
|
+
rescue ::SocketError, ::Errno::ENETDOWN, ::Errno::ETIMEDOUT #, Errno::ENETUNREACH
|
42
|
+
@logger.warn "socket: exception: #{$!}, waiting 5 sec"
|
43
|
+
sleep 5
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
raise Reconnect
|
47
|
+
end
|
48
|
+
@mutex.synchronize do
|
49
|
+
reconnect.call if @socket.nil?
|
50
|
+
begin
|
51
|
+
yield @socket
|
52
|
+
rescue ::SocketError #, Errno::ENETDOWN, Errno::ENETUNREACH
|
53
|
+
@logger.warn "socket: exception: #{$!}, waiting 5 sec"
|
54
|
+
sleep 5
|
55
|
+
reconnect.call
|
56
|
+
end
|
57
|
+
end
|
35
58
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
59
|
+
private_class_method :socket
|
60
|
+
def self.write str
|
61
|
+
socket{ |_| _.send str + "\n", 0 }
|
62
|
+
end
|
63
|
+
def self.log str
|
64
|
+
@logger.warn "> #{str}"
|
65
|
+
write str
|
66
|
+
end
|
67
|
+
@buffer = ""
|
68
|
+
def self.read
|
69
|
+
until i = @buffer.index(?\n)
|
70
|
+
@buffer.concat( socket do |s|
|
71
|
+
return unless select [s], nil, nil, 1
|
72
|
+
s.read(s.nread).tap{ |_| raise SocketError, "empty read" if _.empty? }
|
73
|
+
end )
|
74
|
+
end
|
75
|
+
@buffer.slice!(0..i).chomp
|
76
|
+
end
|
77
|
+
end
|
78
|
+
prev_privmsg_time = ::Time.now
|
79
|
+
queue_thread = ::Thread.new do
|
80
|
+
::Thread.current.abort_on_exception = true
|
81
|
+
loop do
|
82
|
+
sleep [prev_privmsg_time + 5 - Time.now, 0].max
|
83
|
+
addr, msg = Queue.pop
|
84
|
+
fail "I should not PRIVMSG myself" if bot_name == addr = addr.codepoints.pack("U*").tr("\x00\x0A\x0D", "")
|
85
|
+
privmsg = "PRIVMSG #{addr} :#{msg.to_s.codepoints.pack("U*").chomp[/^(\x01*)(.*)/m,2].gsub("\x00", "[NUL]").gsub("\x0A", "[LF]").gsub("\x0D", "[CR]")}"
|
86
|
+
privmsg[-4..-1] = "..." until privmsg.bytesize <= 475
|
87
|
+
prev_privmsg_time = ::Time.now
|
88
|
+
socket.log privmsg
|
89
|
+
end
|
90
|
+
end
|
40
91
|
|
41
|
-
|
42
|
-
|
92
|
+
# https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
|
93
|
+
loop do
|
94
|
+
Queue.clear
|
95
|
+
prev_socket_time = prev_privmsg_time = ::Time.now
|
43
96
|
loop do
|
44
|
-
|
45
|
-
|
46
|
-
next
|
47
|
-
addr = addr.codepoints.pack("U*").tr("\x00\x0A\x0D", "")
|
48
|
-
fail "I should not PRIVMSG myself" if addr == bot_name
|
49
|
-
msg = msg.to_s.codepoints.pack("U*").chomp[/^(\x01*)(.*)/m,2].gsub("\x00", "[NUL]").gsub("\x0A", "[LF]").gsub("\x0D", "[CR]")
|
50
|
-
privmsg = "PRIVMSG #{addr} :#{msg}"
|
51
|
-
privmsg[-4..-1] = "..." until privmsg.bytesize <= 475
|
52
|
-
prev_socket_time = prev_privmsg_time = Time.now
|
53
|
-
socket_send.call privmsg
|
54
|
-
break
|
55
|
-
end until self.queue.empty? if prev_privmsg_time + 5 < Time.now || server == "localhost"
|
56
|
-
|
57
|
-
unless _ = Kernel::select([socket], nil, nil, 1)
|
58
|
-
break if Time.now - prev_socket_time > 300
|
59
|
-
next
|
97
|
+
unless socket_str = socket.read
|
98
|
+
socket.instance_variable_set :@socket, nil if ::Time.now - prev_socket_time > 300
|
99
|
+
next ::Thread.pass
|
60
100
|
end
|
61
|
-
prev_socket_time = Time.now
|
62
|
-
socket_str = _[0][0].gets chomp: true
|
63
|
-
break unless socket_str
|
101
|
+
prev_socket_time = ::Time.now
|
64
102
|
str = socket_str.force_encoding("utf-8").scrub
|
65
103
|
if /\A:\S+ 372 /.match? str # MOTD
|
66
104
|
logger.debug "< #{str}"
|
@@ -72,75 +110,191 @@ module NakiIRCBot
|
|
72
110
|
break if /\AERROR :Closing Link: /.match? str
|
73
111
|
|
74
112
|
# if str[/^:\S+ 433 * #{Regexp.escape bot_name} :Nickname is already in use\.$/]
|
75
|
-
#
|
113
|
+
# socket_log.call "NICK #{bot_name + "_"}"
|
76
114
|
# next
|
77
115
|
# end
|
78
116
|
|
79
117
|
# next socket.send("JOIN #{$2}"+"\n"),0 if str[/^:(.+?)!\S+ KICK (\S+) #{Regexp.escape bot_name} /i]
|
80
118
|
case str
|
81
|
-
when /\A:[a-z.]+ 001 #{Regexp.escape bot_name} :Welcome to the #{Regexp.escape welcome001} #{Regexp.escape bot_name}\z/
|
119
|
+
when /\A:[a-z.]+ 001 #{::Regexp.escape bot_name} :Welcome to the #{::Regexp.escape welcome001} #{::Regexp.escape bot_name}\z/
|
82
120
|
# we join only when we are sure we are on the correct server
|
83
121
|
# TODO: maybe abort if the server is wrong?
|
84
|
-
|
122
|
+
socket.log "JOIN #{channels.join ","}"
|
123
|
+
next
|
85
124
|
|
86
|
-
when /\A:tmi.twitch.tv 001 #{Regexp.escape bot_name} :Welcome, GLHF!\z/
|
87
|
-
|
88
|
-
|
125
|
+
when /\A:tmi.twitch.tv 001 #{::Regexp.escape bot_name} :Welcome, GLHF!\z/
|
126
|
+
socket.log "JOIN #{channels.join ","}"
|
127
|
+
socket.log "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands"
|
128
|
+
tags = true
|
89
129
|
next
|
90
|
-
when /\A:NickServ!NickServ@services\. NOTICE #{Regexp.escape bot_name} :This nickname is registered. Please choose a different nickname, or identify via \x02\/msg NickServ identify <password>\x02\.\z/,
|
91
|
-
/\A:NickServ!NickServ@services\.libera\.chat NOTICE #{Regexp.escape bot_name} :This nickname is registered. Please choose a different nickname, or identify via \x02\/msg NickServ IDENTIFY #{Regexp.escape bot_name} <password>\x02\z/
|
130
|
+
when /\A:NickServ!NickServ@services\. NOTICE #{::Regexp.escape bot_name} :This nickname is registered. Please choose a different nickname, or identify via \x02\/msg NickServ identify <password>\x02\.\z/,
|
131
|
+
/\A:NickServ!NickServ@services\.libera\.chat NOTICE #{::Regexp.escape bot_name} :This nickname is registered. Please choose a different nickname, or identify via \x02\/msg NickServ IDENTIFY #{Regexp.escape bot_name} <password>\x02\z/
|
92
132
|
abort "no password" unless password
|
93
|
-
logger.
|
94
|
-
next socket.
|
133
|
+
logger.warn "password"
|
134
|
+
next socket.write "PRIVMSG NickServ :identify #{bot_name} #{password.strip}"
|
95
135
|
# when /\A:[a-z]+\.libera\.chat CAP \* LS :/
|
96
|
-
# next
|
136
|
+
# next socket_log "CAP REQ :sasl" if $'.split.include? "sasl"
|
97
137
|
when /\A:[a-z]+\.libera\.chat CAP \* ACK :sasl\z/
|
98
|
-
next
|
138
|
+
next socket.log "AUTHENTICATE PLAIN"
|
99
139
|
when /\AAUTHENTICATE \+\z/
|
100
|
-
logger.
|
101
|
-
next socket.
|
140
|
+
logger.warn "password"
|
141
|
+
next socket.write "AUTHENTICATE #{::Base64.strict_encode64 "\0#{identity || bot_name}\0#{password}"}"
|
102
142
|
when /\A:[a-z]+\.libera\.chat 903 #{bot_name} :SASL authentication successful\z/
|
103
|
-
next
|
143
|
+
next socket.log "CAP END"
|
104
144
|
|
105
145
|
when /\APING :/
|
106
|
-
next socket.
|
107
|
-
when /\A:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\x01VERSION\x01\z/
|
108
|
-
next
|
146
|
+
next socket.write "PONG :#{$'}" # Quakenet uses timestamp, Freenode and Twitch use server name
|
147
|
+
when /\A:([^!]+)!\S+ PRIVMSG #{::Regexp.escape bot_name} :\x01VERSION\x01\z/
|
148
|
+
next socket.log "NOTICE #{$1} :\x01VERSION name 0.0.0\x01"
|
109
149
|
# when /^:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\001PING (\d+)\001$/
|
110
|
-
#
|
150
|
+
# socket_log.call "NOTICE",$1,"\001PING #{rand 10000000000}\001"
|
111
151
|
# when /^:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\001TIME\001$/
|
112
|
-
#
|
113
|
-
when /\A#{'\S+ ' if tags}:(?<who>[^!]+)!\S+ PRIVMSG (?<where>\S+) :(?<what>.+)/
|
114
|
-
next( if processors.empty?
|
115
|
-
self.queue.push [master_name, "nothing to reload"]
|
116
|
-
else
|
117
|
-
processors.each do |processor|
|
118
|
-
self.queue.push [master_name, "reloading #{processor}"]
|
119
|
-
load File.absolute_path processor
|
120
|
-
end
|
121
|
-
end ) if $~.named_captures == {"who"=>master_name, "where"=>bot_name, "what"=>"#{masterword.strip} reload"}
|
152
|
+
# socket_log.call "NOTICE",$1,"\001TIME 6:06:06, 6 Jun 06\001"
|
122
153
|
end
|
123
154
|
|
124
155
|
begin
|
125
|
-
yield str,
|
126
|
-
|
127
|
-
|
128
|
-
|
156
|
+
yield str,
|
157
|
+
->(where, what){ Queue.push [where, what] },
|
158
|
+
->(new_password){ password.replace new_password; socket.instance_variable_set :@socket, nil },
|
159
|
+
*/\A#{'\S+ ' if tags}:(?<who>[^!]+)!\S+ PRIVMSG (?<where>\S+) :(?<what>.+)/.match(str).to_a.drop(1).tap{ |who, where, what|
|
160
|
+
logger.warn "#{where} <#{who}> #{what}" if what
|
161
|
+
}
|
162
|
+
rescue
|
163
|
+
puts $!.full_message
|
164
|
+
Queue.push ["##{bot_name}", "error: #{$!}, #{$!.backtrace.first}"]
|
129
165
|
end
|
130
166
|
|
131
|
-
rescue
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
self.queue.push [master_name, "unhandled error: #{e}"]
|
139
|
-
sleep 5
|
140
|
-
end
|
167
|
+
rescue Reconnect
|
168
|
+
# https://ircv3.net/specs/extensions/sasl-3.1.html
|
169
|
+
socket.log "CAP REQ :sasl" if password
|
170
|
+
socket.write "PASS #{password.strip}" # https://dev.twitch.tv/docs/irc/authenticate-bot/
|
171
|
+
socket.log "NICK #{bot_name}"
|
172
|
+
socket.log "USER #{bot_name} #{bot_name} #{bot_name} #{bot_name}"
|
173
|
+
|
141
174
|
end
|
142
175
|
|
143
176
|
end
|
144
177
|
|
145
178
|
end
|
179
|
+
|
180
|
+
module Common
|
181
|
+
def self.ping add_to_queue, what
|
182
|
+
return add_to_queue.call what[1..-1].tr "iI", "oO" if "\\ping" == what.downcase
|
183
|
+
return add_to_queue.call what[1..-1].tr "иИ", "оO" if "\\пинг" == what.downcase
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.parse_log path, bot_name
|
188
|
+
require "time"
|
189
|
+
get_tags = lambda do |str|
|
190
|
+
str[1..-1].split(?;).map do |pair|
|
191
|
+
(a, b) = pair.split ?=
|
192
|
+
fail if a.empty?
|
193
|
+
[a, b]
|
194
|
+
end.to_h
|
195
|
+
end
|
196
|
+
File.new(path).each(chomp: true).drop(1).map do |line|
|
197
|
+
case line
|
198
|
+
when /\AD, /
|
199
|
+
when /\A[IW], \[(\S+) #\d+\] (?:INFO|WARN) -- #{bot_name}: (.+)\z/
|
200
|
+
_ = Base64.decode64($2).force_encoding "utf-8"
|
201
|
+
[
|
202
|
+
DateTime.parse($1).to_time,
|
203
|
+
*case _
|
204
|
+
when /\A> /,
|
205
|
+
"< :tmi.twitch.tv 002 #{bot_name} :Your host is tmi.twitch.tv",
|
206
|
+
"< :tmi.twitch.tv 003 #{bot_name} :This server is rather new",
|
207
|
+
"< :tmi.twitch.tv 004 #{bot_name} :-",
|
208
|
+
"< :tmi.twitch.tv 375 #{bot_name} :-",
|
209
|
+
"< :tmi.twitch.tv 376 #{bot_name} :>",
|
210
|
+
/\A< :#{bot_name}!#{bot_name}@#{bot_name}\.tmi\.twitch\.tv JOIN #[a-z\d_]+\z/,
|
211
|
+
/\A< :#{bot_name}\.tmi\.twitch\.tv 353 #{bot_name} /,
|
212
|
+
/\A< :#{bot_name}\.tmi\.twitch\.tv 366 #{bot_name} /,
|
213
|
+
"< :tmi.twitch.tv CAP * ACK :twitch.tv/membership twitch.tv/tags twitch.tv/commands",
|
214
|
+
"< :tmi.twitch.tv CAP * NAK :sasl",
|
215
|
+
"< :tmi.twitch.tv NOTICE * :Improperly formatted auth",
|
216
|
+
"< :tmi.twitch.tv RECONNECT"
|
217
|
+
when /\A< (\S+) :tmi\.twitch\.tv USERSTATE ##{bot_name}\z/ # wtf?
|
218
|
+
when /\Aexception: /
|
219
|
+
when "reconnect",
|
220
|
+
"socket: reconnecting",
|
221
|
+
/\Asocket: exception: /,
|
222
|
+
"< :tmi.twitch.tv 001 #{bot_name} :Welcome, GLHF!"
|
223
|
+
[nil, "RECONNECT"]
|
224
|
+
when /\A< :([^\s!]+)!\1@\1\.tmi\.twitch\.tv (JOIN|PART) #([a-z\d_]+)\z/
|
225
|
+
[$3, $2, $1]
|
226
|
+
when /\A#([a-z\d_]+) <(\S+)> (.+)\z/
|
227
|
+
[$1, "PRIVMSG", $2, $3]
|
228
|
+
when /\A< (\S+) :tmi\.twitch\.tv CLEARMSG #([a-z\d_]+) :((?:\S.*)?\S)\z/
|
229
|
+
[$2, "CLEARMSG", get_tags[$1].fetch("login"), $3]
|
230
|
+
when /\A< (\S+) :tmi\.twitch\.tv CLEARCHAT #([a-z\d_]+) :([^\s!]+)\z/
|
231
|
+
[$2, "CLEARCHAT", $3, get_tags[$1].fetch("target-user-id")]
|
232
|
+
when /\A< @emote-only=0;room-id=\d+ :tmi\.twitch\.tv ROOMSTATE #([a-z\d_]+)\z/
|
233
|
+
[$1, "ROOMSTATE EMOTEONLY 0"]
|
234
|
+
when /\A< @emote-only=1;room-id=\d+ :tmi\.twitch\.tv ROOMSTATE #([a-z\d_]+)\z/
|
235
|
+
[$1, "ROOMSTATE EMOTEONLY 1"]
|
236
|
+
when /\A< @msg-id=emote_only_off :tmi\.twitch\.tv NOTICE #([a-z\d_]+) :This room is no longer in emote-only mode\.\z/
|
237
|
+
[$1, "EMOTE_ONLY_OFF"]
|
238
|
+
when /\A< @msg-id=emote_only_on :tmi\.twitch\.tv NOTICE #([a-z\d_]+) :This room is now in emote-only mode\.\z/
|
239
|
+
[$1, "EMOTE_ONLY_ON"]
|
240
|
+
when /\A< @followers-only=-1;room-id=\d+ :tmi\.twitch\.tv ROOMSTATE #([a-z\d_]+)\z/
|
241
|
+
[$1, "ROOMSTATE FOLLOWERSONLY 0"]
|
242
|
+
when /\A< @msg-id=followers_off :tmi\.twitch\.tv NOTICE #([a-z\d_]+) :This room is no longer in followers-only mode\.\z/
|
243
|
+
[$1, "FOLLOWERS_ONLY_OFF"]
|
244
|
+
when /\A< :tmi\.twitch\.tv HOSTTARGET #([a-z\d_]+) :(\S+) (\d+)\z/
|
245
|
+
next if "-" == $2 # wtf?
|
246
|
+
fail unless $2 == $2.downcase
|
247
|
+
[$1, "HOST", $2, $3.to_i]
|
248
|
+
when /\A< @msg-id=host_target_went_offline :tmi\.twitch\.tv NOTICE #([a-z\d_]+) :(\S+) has gone offline\. Exiting host mode\.\z/
|
249
|
+
fail unless $2 == $2.downcase
|
250
|
+
[$1, "HOST_TARGET_WENT_OFFLINE", $2]
|
251
|
+
when /\A< @msg-id=host_on :tmi\.twitch\.tv NOTICE #([a-z\d_]+) :Now hosting (\S+)\.\z/
|
252
|
+
[$1, "NOTICE HOST", $2]
|
253
|
+
when /\A< (\S+) :tmi\.twitch\.tv USERNOTICE #([a-z\d_]+)(?: :((?:\S.*)?\S))?\z/
|
254
|
+
tags = get_tags[$1]
|
255
|
+
fail unless tags.fetch("display-name").downcase == tags.fetch("login")
|
256
|
+
[
|
257
|
+
$2,
|
258
|
+
tags["msg-id"].upcase,
|
259
|
+
*case tags.fetch "msg-id"
|
260
|
+
when "raid"
|
261
|
+
fail if $3
|
262
|
+
[tags.fetch("display-name"), tags.fetch("msg-param-viewerCount").to_i.tap{ |_| fail unless _ > 0 }]
|
263
|
+
when "resub"
|
264
|
+
[tags.fetch("display-name"), *$3]
|
265
|
+
when "sub"
|
266
|
+
fail if $3
|
267
|
+
[tags.fetch("display-name")]
|
268
|
+
when "submysterygift"
|
269
|
+
# fail unless tags["msg-param-mass-gift-count"] == "1"
|
270
|
+
# fail unless tags["msg-param-sender-count"] == "1"
|
271
|
+
fail if $3
|
272
|
+
[tags.fetch("display-name"), tags.fetch("msg-param-mass-gift-count")]
|
273
|
+
when "subgift"
|
274
|
+
fail unless "1" == tags.fetch("msg-param-gift-months")
|
275
|
+
# fail unless tags["msg-param-months"] == "1"
|
276
|
+
fail if $3
|
277
|
+
[tags.fetch("display-name")]
|
278
|
+
when "bitsbadgetier"
|
279
|
+
fail unless $3
|
280
|
+
[tags.fetch("display-name")]
|
281
|
+
when "primepaidupgrade"
|
282
|
+
fail if $3
|
283
|
+
[tags.fetch("display-name")]
|
284
|
+
when "viewermilestone"
|
285
|
+
fail if $3
|
286
|
+
[tags.fetch("display-name")]
|
287
|
+
else
|
288
|
+
fail "unknown USERNOTICE: #{[tags["msg-id"], _, $3].inspect}"
|
289
|
+
end
|
290
|
+
]
|
291
|
+
else
|
292
|
+
fail "bad log line: #{_.inspect}"
|
293
|
+
end
|
294
|
+
]
|
295
|
+
else
|
296
|
+
fail line.inspect
|
297
|
+
end
|
298
|
+
end.compact.tap{ |_| fail unless 1 == _.map(&:first).map(&:day).uniq.size }
|
299
|
+
end
|
146
300
|
end
|
data/nakiircbot.gemspec
CHANGED
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nakiircbot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Maslov aka Nakilon
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description:
|
14
14
|
email: nakilon@gmail.com
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
@@ -19,12 +19,12 @@ files:
|
|
19
19
|
- LICENSE
|
20
20
|
- lib/nakiircbot.rb
|
21
21
|
- nakiircbot.gemspec
|
22
|
-
homepage:
|
22
|
+
homepage:
|
23
23
|
licenses:
|
24
24
|
- MIT
|
25
25
|
metadata:
|
26
26
|
source_code_uri: https://github.com/nakilon/nakiircbot
|
27
|
-
post_install_message:
|
27
|
+
post_install_message:
|
28
28
|
rdoc_options: []
|
29
29
|
require_paths:
|
30
30
|
- lib
|
@@ -39,8 +39,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
requirements: []
|
42
|
-
rubygems_version: 3.
|
43
|
-
signing_key:
|
42
|
+
rubygems_version: 3.4.13
|
43
|
+
signing_key:
|
44
44
|
specification_version: 4
|
45
45
|
summary: IRC bot framework
|
46
46
|
test_files: []
|