nakiircbot 0.2.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nakiircbot.rb +242 -88
  3. data/nakiircbot.gemspec +1 -1
  4. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92edbf539aabbbac1e6b60cd752b5cb8051eac5cac7fb1a7f66d62fc3d150f89
4
- data.tar.gz: 2d8b504a2f9fa52e2648aaa14bac414b1d21c62072367b4f21de8fdbfbb53754
3
+ metadata.gz: 0c070c5f505213c2ca011f3a87911603f2b327c2787406c5ffe2b9e8fdc60495
4
+ data.tar.gz: 145078acb492b0c2afb570be54145eeaca0fa749978b1dcbce59470ac7f84a74
5
5
  SHA512:
6
- metadata.gz: 8601bfe95fa4066b256489cf129e7371f5f879bae8959a6ae494fd1c8cf014edc03df16df9449f434053893873ee52b7d92fffc8f8c018e6ac3c5227757ea348
7
- data.tar.gz: c902841c4e88f71122ba2d0d5b32e204e337cf9d227d35fbaf2771ba450932e9ec2558ecd2f73443b8af3e7d81d6b0de9cf29272789f25872fd28a98853bd286
6
+ metadata.gz: 4277a56a3a649aca36bf058cb89bbf6a2ac17de4c9388ae22d341d2de234239d4252d7335c422f41bab6b5fc4e954491900660d4b851eb22040f50de604deed9
7
+ data.tar.gz: c62d1a88df3c5b58aa3bfce9f0f0b52a7804ec85ca32c3e9e82ebd5dd0ff8fa61412a23553213872a497df505d4c8f666736b5f4fe2332f9193a74c6e8e79852
data/lib/nakiircbot.rb CHANGED
@@ -1,66 +1,104 @@
1
1
  module NakiIRCBot
2
- class << self
3
- attr_accessor :queue
4
- end
5
- self.queue = []
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
- original_formatter = Logger::Formatter.new
15
- logger = Logger.new "logs/txt", "daily",
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 = ENV["LOGLEVEL_#{name}"].to_sym if ENV.include? "LOGLEVEL_#{name}"
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://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
26
- loop do
27
- logger.info "reconnect"
28
- require "socket"
29
- socket = TCPSocket.new server, port
30
-
31
- # https://stackoverflow.com/a/49476047/322020
32
- socket_send = lambda do |str|
33
- logger.info "> #{str}"
34
- socket.send str + "\n", 0
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
- # socket.send "PASS #{password.strip}\n", 0 if twitch
37
- socket_send.call "CAP REQ :sasl" if password
38
- socket_send.call "NICK #{bot_name}"
39
- socket_send.call "USER #{bot_name} #{bot_name} #{bot_name} #{bot_name}" #unless twitch
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
- self.queue = []
42
- prev_socket_time = prev_privmsg_time = Time.now
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
- begin
45
- addr, msg = self.queue.shift
46
- next unless addr && msg # TODO: how is it possible to have only one of them?
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
- # socket_send.call "NICK #{bot_name + "_"}"
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
- next socket_send.call "JOIN #{channels.join ","}"
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
- socket_send.call "JOIN #{channels.join ","}"
88
- socket_send.call "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands"
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.info "password"
94
- next socket.send "PRIVMSG NickServ :identify #{bot_name} #{password.strip}\n", 0
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 socket_send "CAP REQ :sasl" if $'.split.include? "sasl"
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 socket_send.call "AUTHENTICATE PLAIN"
138
+ next socket.log "AUTHENTICATE PLAIN"
99
139
  when /\AAUTHENTICATE \+\z/
100
- logger.info "password"
101
- next socket.send "AUTHENTICATE #{Base64.strict_encode64 "\0#{identity || bot_name}\0#{password}"}\n", 0
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 socket_send.call "CAP END"
143
+ next socket.log "CAP END"
104
144
 
105
145
  when /\APING :/
106
- next socket.send "PONG :#{$'}\n", 0 # Quakenet uses timestamp, Freenode and Twitch use server name
107
- when /\A:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\x01VERSION\x01\z/
108
- next socket_send.call "NOTICE #{$1} :\x01VERSION name 0.0.0\x01"
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
- # socket_send.call "NOTICE",$1,"\001PING #{rand 10000000000}\001"
150
+ # socket_log.call "NOTICE",$1,"\001PING #{rand 10000000000}\001"
111
151
  # when /^:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\001TIME\001$/
112
- # socket_send.call "NOTICE",$1,"\001TIME 6:06:06, 6 Jun 06\001"
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, ->(where, what){ self.queue.push [where, what] }
126
- rescue => e
127
- puts e.full_message
128
- self.queue.push [master_name, "yield error: #{e}"]
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 => e
132
- puts e.full_message
133
- case e
134
- when Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT, Errno::EPIPE
135
- sleep 5
136
- break
137
- else
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "nakiircbot"
3
- spec.version = "0.2.0"
3
+ spec.version = "1.1.0"
4
4
  spec.summary = "IRC bot framework"
5
5
 
6
6
  spec.author = "Victor Maslov aka Nakilon"
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: 0.2.0
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-03-26 00:00:00.000000000 Z
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.2.22
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: []