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.
- checksums.yaml +4 -4
- data/lib/nakiircbot.rb +65 -40
- data/nakiircbot.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cecb68b5866e9a3ecb2bdff832d44c97b2c28dcec112ecda32f7e5a3e95a6da0
|
4
|
+
data.tar.gz: c4618d0827639dbd5d18d0bc340ead4d5bb54b140b04081e91782fc51cfbbd05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
5
|
-
|
6
|
-
def self.start server, port, bot_name,
|
7
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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 +
|
83
|
-
addr, msg =
|
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
|
-
|
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.
|
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
|
-
|
104
|
-
|
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 #{
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
162
|
+
chat_queue.push ["##{bot_name}", "error"]
|
165
163
|
end
|
166
164
|
|
167
|
-
rescue
|
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
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.
|
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:
|
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.
|
42
|
+
rubygems_version: 3.3.27
|
43
43
|
signing_key:
|
44
44
|
specification_version: 4
|
45
45
|
summary: IRC bot framework
|