nakiircbot 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|