nakiircbot 1.1.0 → 1.3.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nakiircbot.rb +65 -40
  3. data/nakiircbot.gemspec +1 -1
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c070c5f505213c2ca011f3a87911603f2b327c2787406c5ffe2b9e8fdc60495
4
- data.tar.gz: 145078acb492b0c2afb570be54145eeaca0fa749978b1dcbce59470ac7f84a74
3
+ metadata.gz: cecb68b5866e9a3ecb2bdff832d44c97b2c28dcec112ecda32f7e5a3e95a6da0
4
+ data.tar.gz: c4618d0827639dbd5d18d0bc340ead4d5bb54b140b04081e91782fc51cfbbd05
5
5
  SHA512:
6
- metadata.gz: 4277a56a3a649aca36bf058cb89bbf6a2ac17de4c9388ae22d341d2de234239d4252d7335c422f41bab6b5fc4e954491900660d4b851eb22040f50de604deed9
7
- data.tar.gz: c62d1a88df3c5b58aa3bfce9f0f0b52a7804ec85ca32c3e9e82ebd5dd0ff8fa61412a23553213872a497df505d4c8f666736b5f4fe2332f9193a74c6e8e79852
6
+ metadata.gz: dca52aa83819fee1b9103fb6135175886e9b4080cacd320bb9a0b9232466de9c0cbe8e6dc76bb26c9a78d6e92fdb2fa8896ceb12bddfe985fc45585bbc551da0
7
+ data.tar.gz: db6cf8ad3fd8c920a1e19eb2967f4d3c43c8ee75b43a74ab79f95bdbc111f3fc4b61c323b258b02990898292a9b78b151375178070bf807746b5750ac8db4b48
data/lib/nakiircbot.rb CHANGED
@@ -1,16 +1,14 @@
1
1
  module NakiIRCBot
2
2
  require "base64"
3
3
 
4
- Reconnect = ::Class.new ::RuntimeError
5
- Queue = ::Queue.new
6
- def self.start server, port, bot_name, master_name, welcome001, *channels, identity: nil, password: nil, masterword: nil, processors: [], tags: false
7
- # @@channels.replace channels.dup
4
+ ReconnectError = ::Class.new ::RuntimeError
5
+ CHAT_QUEUE_DELAY = 5
6
+ def self.start server, port, bot_name, *channels, owner: nil, identity: nil, password: nil, masterword: nil, processors: [], tags: false
7
+ chat_queue = ::Queue.new
8
8
 
9
- abort "matching bot_name and master_name may cause infinite recursion" if bot_name == master_name
10
9
  require "fileutils"
11
10
  ::FileUtils.mkdir_p "logs"
12
11
  require "logger"
13
- # TODO: check how I've implemented the logger in trovobot
14
12
  original_formatter = ::Logger::Formatter.new
15
13
  logger = ::Logger.new "logs/txt", "daily",
16
14
  progname: bot_name, datetime_format: "%y%m%d %H%M%S",
@@ -23,8 +21,6 @@ module NakiIRCBot
23
21
  logger.level = ::ENV["LOGLEVEL_#{name}"].to_sym if ::ENV.include? "LOGLEVEL_#{name}"
24
22
  puts "#{name} logger.level = #{logger.level}"
25
23
 
26
- # https://stackoverflow.com/a/49476047/322020
27
-
28
24
  require "socket"
29
25
  socket = ::Module.new do
30
26
  @logger = logger
@@ -32,6 +28,9 @@ module NakiIRCBot
32
28
  @port = port
33
29
 
34
30
  @socket = nil
31
+ def self.update
32
+ @socket = nil
33
+ end
35
34
  @mutex = ::Mutex.new
36
35
  def self.socket
37
36
  reconnect = lambda do
@@ -43,7 +42,7 @@ module NakiIRCBot
43
42
  sleep 5
44
43
  retry
45
44
  end
46
- raise Reconnect
45
+ raise ReconnectError
47
46
  end
48
47
  @mutex.synchronize do
49
48
  reconnect.call if @socket.nil?
@@ -57,7 +56,7 @@ module NakiIRCBot
57
56
  end
58
57
  end
59
58
  private_class_method :socket
60
- def self.write str
59
+ def self.write str # send to socket without logging
61
60
  socket{ |_| _.send str + "\n", 0 }
62
61
  end
63
62
  def self.log str
@@ -67,20 +66,20 @@ module NakiIRCBot
67
66
  @buffer = ""
68
67
  def self.read
69
68
  until i = @buffer.index(?\n)
70
- @buffer.concat( socket do |s|
69
+ @buffer.concat socket{ |s|
71
70
  return unless select [s], nil, nil, 1
72
- s.read(s.nread).tap{ |_| raise SocketError, "empty read" if _.empty? }
73
- end )
71
+ s.read(s.nread).tap{ |_| raise ::SocketError, "empty read" if _.empty? }
72
+ }
74
73
  end
75
74
  @buffer.slice!(0..i).chomp
76
75
  end
77
76
  end
78
77
  prev_privmsg_time = ::Time.now
79
- queue_thread = ::Thread.new do
80
- ::Thread.current.abort_on_exception = true
78
+ chat_queue_thread = ::Thread.new do
79
+ ::Thread.current.abort_on_exception = true # it has never happened, right? so I don't know what it would cause really
81
80
  loop do
82
- sleep [prev_privmsg_time + 5 - Time.now, 0].max
83
- addr, msg = Queue.pop
81
+ sleep [prev_privmsg_time + CHAT_QUEUE_DELAY - ::Time.now, 0].max
82
+ addr, msg = chat_queue.pop
84
83
  fail "I should not PRIVMSG myself" if bot_name == addr = addr.codepoints.pack("U*").tr("\x00\x0A\x0D", "")
85
84
  privmsg = "PRIVMSG #{addr} :#{msg.to_s.codepoints.pack("U*").chomp[/^(\x01*)(.*)/m,2].gsub("\x00", "[NUL]").gsub("\x0A", "[LF]").gsub("\x0D", "[CR]")}"
86
85
  privmsg[-4..-1] = "..." until privmsg.bytesize <= 475
@@ -89,44 +88,43 @@ module NakiIRCBot
89
88
  end
90
89
  end
91
90
 
91
+ # https://stackoverflow.com/a/49476047/322020 -- about PASS, NICK, USER
92
92
  # https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
93
93
  loop do
94
- Queue.clear
94
+ chat_queue.clear
95
95
  prev_socket_time = prev_privmsg_time = ::Time.now
96
96
  loop do
97
97
  unless socket_str = socket.read
98
- socket.instance_variable_set :@socket, nil if ::Time.now - prev_socket_time > 300
98
+ socket.update if ::Time.now - prev_socket_time > 300
99
99
  next ::Thread.pass
100
100
  end
101
101
  prev_socket_time = ::Time.now
102
- str = socket_str.force_encoding("utf-8").scrub
103
- if /\A:\S+ 372 /.match? str # MOTD
104
- logger.debug "< #{str}"
105
- elsif /\APING :/.match? str
102
+ case str = socket_str.force_encoding("utf-8").scrub
103
+ when /\A:\S+ 372 /, # MOTD
104
+ /\APING :/
106
105
  logger.debug "< #{str}"
107
106
  else
108
107
  logger.info "< #{str}"
108
+ next socket.update if /\AERROR :Closing Link: /.match? str
109
109
  end
110
- break if /\AERROR :Closing Link: /.match? str
111
110
 
112
111
  # if str[/^:\S+ 433 * #{Regexp.escape bot_name} :Nickname is already in use\.$/]
113
112
  # socket_log.call "NICK #{bot_name + "_"}"
114
113
  # next
115
114
  # end
116
115
 
116
+ # https://www.alien.net.au/irc/irc2numerics.html
117
+
117
118
  # next socket.send("JOIN #{$2}"+"\n"),0 if str[/^:(.+?)!\S+ KICK (\S+) #{Regexp.escape bot_name} /i]
118
119
  case str
119
- when /\A:[a-z.]+ 001 #{::Regexp.escape bot_name} :Welcome to the #{::Regexp.escape welcome001} #{::Regexp.escape bot_name}\z/
120
- # we join only when we are sure we are on the correct server
121
- # TODO: maybe abort if the server is wrong?
122
- socket.log "JOIN #{channels.join ","}"
123
- next
124
-
125
120
  when /\A:tmi.twitch.tv 001 #{::Regexp.escape bot_name} :Welcome, GLHF!\z/
126
- socket.log "JOIN #{channels.join ","}"
121
+ channels.each_slice(10){ |slice| socket.log "JOIN #{slice.join ","}" }
127
122
  socket.log "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands"
128
123
  tags = true
129
124
  next
125
+ when /\A:[a-z.]+ 001 #{::Regexp.escape bot_name} :Welcome[ ,]/
126
+ socket.log "JOIN #{channels.join ","}"
127
+ next
130
128
  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
129
  /\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/
132
130
  abort "no password" unless password
@@ -139,7 +137,7 @@ module NakiIRCBot
139
137
  when /\AAUTHENTICATE \+\z/
140
138
  logger.warn "password"
141
139
  next socket.write "AUTHENTICATE #{::Base64.strict_encode64 "\0#{identity || bot_name}\0#{password}"}"
142
- when /\A:[a-z]+\.libera\.chat 903 #{bot_name} :SASL authentication successful\z/
140
+ when /\A:[a-z]+\.libera\.chat 903 #{::Regexp.escape bot_name} :SASL authentication successful\z/
143
141
  next socket.log "CAP END"
144
142
 
145
143
  when /\APING :/
@@ -153,20 +151,21 @@ module NakiIRCBot
153
151
  end
154
152
 
155
153
  begin
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
- }
154
+ who, where, what = */\A#{'\S+ ' if tags}:(?<who>[^!]+)!\S+ PRIVMSG (?<where>\S+) :(?<what>.+)/.match(str).to_a.drop(1)
155
+ logger.warn "#{where} <#{who}> #{what}" if what
156
+ yield str, who, where, what,
157
+ add_to_chat_queue: ->(where, what){ chat_queue.push [where, what] },
158
+ socket_write_and_log: socket.public_method(:log),
159
+ restart_with_new_password: ->(new_password){ password.replace new_password; socket.update }
162
160
  rescue
163
161
  puts $!.full_message
164
- Queue.push ["##{bot_name}", "error: #{$!}, #{$!.backtrace.first}"]
162
+ chat_queue.push ["##{bot_name}", "error"]
165
163
  end
166
164
 
167
- rescue Reconnect
165
+ rescue ReconnectError
168
166
  # https://ircv3.net/specs/extensions/sasl-3.1.html
169
167
  socket.log "CAP REQ :sasl" if password
168
+ logger.warn "password"
170
169
  socket.write "PASS #{password.strip}" # https://dev.twitch.tv/docs/irc/authenticate-bot/
171
170
  socket.log "NICK #{bot_name}"
172
171
  socket.log "USER #{bot_name} #{bot_name} #{bot_name} #{bot_name}"
@@ -175,6 +174,8 @@ module NakiIRCBot
175
174
 
176
175
  end
177
176
 
177
+ ensure
178
+ chat_queue_thread.kill while chat_queue_thread.alive?
178
179
  end
179
180
 
180
181
  module Common
@@ -201,6 +202,8 @@ module NakiIRCBot
201
202
  [
202
203
  DateTime.parse($1).to_time,
203
204
  *case _
205
+ when /\A> PRIVMSG #([a-z\d_]+) :/
206
+ [$1, ">", bot_name, $']
204
207
  when /\A> /,
205
208
  "< :tmi.twitch.tv 002 #{bot_name} :Your host is tmi.twitch.tv",
206
209
  "< :tmi.twitch.tv 003 #{bot_name} :This server is rather new",
@@ -217,6 +220,7 @@ module NakiIRCBot
217
220
  when /\A< (\S+) :tmi\.twitch\.tv USERSTATE ##{bot_name}\z/ # wtf?
218
221
  when /\Aexception: /
219
222
  when "reconnect",
223
+ "password",
220
224
  "socket: reconnecting",
221
225
  /\Asocket: exception: /,
222
226
  "< :tmi.twitch.tv 001 #{bot_name} :Welcome, GLHF!"
@@ -297,4 +301,25 @@ module NakiIRCBot
297
301
  end
298
302
  end.compact.tap{ |_| fail unless 1 == _.map(&:first).map(&:day).uniq.size }
299
303
  end
304
+
305
+ def self.test start
306
+ server = ::TCPServer.new 6667
307
+ ::Thread.new do
308
+ ::Thread.current.abort_on_exception = true
309
+ start.call
310
+ end.tap do |thread|
311
+ socket = server.accept
312
+ begin
313
+ yield \
314
+ ->{ select [socket], nil, nil, 1 },
315
+ ->{ ::Timeout.timeout(1.5){ socket.gets } },
316
+ ->_{ socket.puts _ }
317
+ ensure
318
+ # puts "shutting down test server"
319
+ server.close #rescue Errno::ENOTCONN
320
+ thread.kill while thread.alive?
321
+ end
322
+ end
323
+ end
324
+
300
325
  end
data/nakiircbot.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "nakiircbot"
3
- spec.version = "1.1.0"
3
+ spec.version = "1.3.0"
4
4
  spec.summary = "IRC bot framework"
5
5
 
6
6
  spec.author = "Victor Maslov aka Nakilon"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nakiircbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Maslov aka Nakilon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-18 00:00:00.000000000 Z
11
+ date: 2024-06-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: nakilon@gmail.com
@@ -39,7 +39,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  requirements: []
42
- rubygems_version: 3.4.13
42
+ rubygems_version: 3.3.27
43
43
  signing_key:
44
44
  specification_version: 4
45
45
  summary: IRC bot framework