nakiircbot 1.1.0 → 1.3.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 +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