nakiircbot 1.0.0 → 1.1.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 +72 -64
- 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: 0c070c5f505213c2ca011f3a87911603f2b327c2787406c5ffe2b9e8fdc60495
|
4
|
+
data.tar.gz: 145078acb492b0c2afb570be54145eeaca0fa749978b1dcbce59470ac7f84a74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4277a56a3a649aca36bf058cb89bbf6a2ac17de4c9388ae22d341d2de234239d4252d7335c422f41bab6b5fc4e954491900660d4b851eb22040f50de604deed9
|
7
|
+
data.tar.gz: c62d1a88df3c5b58aa3bfce9f0f0b52a7804ec85ca32c3e9e82ebd5dd0ff8fa61412a23553213872a497df505d4c8f666736b5f4fe2332f9193a74c6e8e79852
|
data/lib/nakiircbot.rb
CHANGED
@@ -1,103 +1,104 @@
|
|
1
1
|
module NakiIRCBot
|
2
2
|
require "base64"
|
3
3
|
|
4
|
-
|
4
|
+
Reconnect = ::Class.new ::RuntimeError
|
5
|
+
Queue = ::Queue.new
|
5
6
|
def self.start server, port, bot_name, master_name, welcome001, *channels, identity: nil, password: nil, masterword: nil, processors: [], tags: false
|
6
7
|
# @@channels.replace channels.dup
|
7
8
|
|
8
9
|
abort "matching bot_name and master_name may cause infinite recursion" if bot_name == master_name
|
9
10
|
require "fileutils"
|
10
|
-
FileUtils.mkdir_p "logs"
|
11
|
+
::FileUtils.mkdir_p "logs"
|
11
12
|
require "logger"
|
12
13
|
# TODO: check how I've implemented the logger in trovobot
|
13
|
-
original_formatter = Logger::Formatter.new
|
14
|
-
logger = Logger.new "logs/txt", "daily",
|
14
|
+
original_formatter = ::Logger::Formatter.new
|
15
|
+
logger = ::Logger.new "logs/txt", "daily",
|
15
16
|
progname: bot_name, datetime_format: "%y%m%d %H%M%S",
|
16
17
|
formatter: lambda{ |severity, datetime, progname, msg|
|
17
18
|
puts "#{datetime.strftime "%H%M%S"} #{severity.to_s[0]} #{progname} #{msg.scrub.inspect[1..-2]}"
|
18
|
-
original_formatter.call severity, datetime, progname, Base64.strict_encode64(msg)
|
19
|
+
original_formatter.call severity, datetime, progname, ::Base64.strict_encode64(msg)
|
19
20
|
# TODO: maybe encode the whole string for a case of invalid progname?
|
20
21
|
}
|
21
|
-
logger.level =
|
22
|
+
logger.level = ::Logger::WARN
|
23
|
+
logger.level = ::ENV["LOGLEVEL_#{name}"].to_sym if ::ENV.include? "LOGLEVEL_#{name}"
|
22
24
|
puts "#{name} logger.level = #{logger.level}"
|
23
25
|
|
24
26
|
# https://stackoverflow.com/a/49476047/322020
|
25
27
|
|
26
28
|
require "socket"
|
27
|
-
|
28
|
-
socket = Module.new do
|
29
|
+
socket = ::Module.new do
|
29
30
|
@logger = logger
|
30
31
|
@server = server
|
31
32
|
@port = port
|
32
33
|
|
33
34
|
@socket = nil
|
34
|
-
|
35
|
-
yield
|
36
|
-
rescue SocketError, Errno::ENETDOWN, Errno::ENETUNREACH
|
37
|
-
@socket = nil
|
38
|
-
@logger.warn "exception: #{$!}, waiting 5 sec"
|
39
|
-
sleep 5
|
40
|
-
retry
|
41
|
-
end
|
42
|
-
private_class_method :rescue_socket
|
35
|
+
@mutex = ::Mutex.new
|
43
36
|
def self.socket
|
44
|
-
|
45
|
-
@logger.warn "
|
46
|
-
|
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
|
47
57
|
end
|
48
58
|
end
|
49
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
|
50
67
|
@buffer = ""
|
51
68
|
def self.read
|
52
69
|
until i = @buffer.index(?\n)
|
53
|
-
@buffer.concat(
|
54
|
-
return unless select [
|
55
|
-
|
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? }
|
56
73
|
end )
|
57
74
|
end
|
58
75
|
@buffer.slice!(0..i).chomp
|
59
76
|
end
|
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
77
|
end
|
68
|
-
prev_privmsg_time = Time.now
|
69
|
-
queue_thread = Thread.new do
|
70
|
-
Thread.current.abort_on_exception = true
|
71
|
-
# Thread.stop
|
78
|
+
prev_privmsg_time = ::Time.now
|
79
|
+
queue_thread = ::Thread.new do
|
80
|
+
::Thread.current.abort_on_exception = true
|
72
81
|
loop do
|
73
82
|
sleep [prev_privmsg_time + 5 - Time.now, 0].max
|
74
|
-
addr, msg =
|
83
|
+
addr, msg = Queue.pop
|
75
84
|
fail "I should not PRIVMSG myself" if bot_name == addr = addr.codepoints.pack("U*").tr("\x00\x0A\x0D", "")
|
76
85
|
privmsg = "PRIVMSG #{addr} :#{msg.to_s.codepoints.pack("U*").chomp[/^(\x01*)(.*)/m,2].gsub("\x00", "[NUL]").gsub("\x0A", "[LF]").gsub("\x0D", "[CR]")}"
|
77
86
|
privmsg[-4..-1] = "..." until privmsg.bytesize <= 475
|
78
|
-
prev_privmsg_time = Time.now
|
87
|
+
prev_privmsg_time = ::Time.now
|
79
88
|
socket.log privmsg
|
80
89
|
end
|
81
90
|
end
|
82
91
|
|
83
92
|
# https://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands
|
84
93
|
loop do
|
85
|
-
|
86
|
-
|
87
|
-
socket.log "CAP REQ :sasl" if password
|
88
|
-
socket.write "PASS #{password.strip}" # https://dev.twitch.tv/docs/irc/authenticate-bot/
|
89
|
-
socket.log "NICK #{bot_name}"
|
90
|
-
socket.log "USER #{bot_name} #{bot_name} #{bot_name} #{bot_name}" #unless twitch
|
91
|
-
|
92
|
-
@queue.clear
|
93
|
-
prev_socket_time = prev_privmsg_time = Time.now
|
94
|
+
Queue.clear
|
95
|
+
prev_socket_time = prev_privmsg_time = ::Time.now
|
94
96
|
loop do
|
95
97
|
unless socket_str = socket.read
|
96
|
-
|
97
|
-
next
|
98
|
+
socket.instance_variable_set :@socket, nil if ::Time.now - prev_socket_time > 300
|
99
|
+
next ::Thread.pass
|
98
100
|
end
|
99
|
-
prev_socket_time = Time.now
|
100
|
-
break unless socket_str
|
101
|
+
prev_socket_time = ::Time.now
|
101
102
|
str = socket_str.force_encoding("utf-8").scrub
|
102
103
|
if /\A:\S+ 372 /.match? str # MOTD
|
103
104
|
logger.debug "< #{str}"
|
@@ -115,18 +116,19 @@ module NakiIRCBot
|
|
115
116
|
|
116
117
|
# next socket.send("JOIN #{$2}"+"\n"),0 if str[/^:(.+?)!\S+ KICK (\S+) #{Regexp.escape bot_name} /i]
|
117
118
|
case str
|
118
|
-
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/
|
119
120
|
# we join only when we are sure we are on the correct server
|
120
121
|
# TODO: maybe abort if the server is wrong?
|
121
|
-
|
122
|
+
socket.log "JOIN #{channels.join ","}"
|
123
|
+
next
|
122
124
|
|
123
|
-
when /\A:tmi.twitch.tv 001 #{Regexp.escape bot_name} :Welcome, GLHF!\z/
|
125
|
+
when /\A:tmi.twitch.tv 001 #{::Regexp.escape bot_name} :Welcome, GLHF!\z/
|
124
126
|
socket.log "JOIN #{channels.join ","}"
|
125
127
|
socket.log "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands"
|
126
128
|
tags = true
|
127
129
|
next
|
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/,
|
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/
|
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/
|
130
132
|
abort "no password" unless password
|
131
133
|
logger.warn "password"
|
132
134
|
next socket.write "PRIVMSG NickServ :identify #{bot_name} #{password.strip}"
|
@@ -136,13 +138,13 @@ module NakiIRCBot
|
|
136
138
|
next socket.log "AUTHENTICATE PLAIN"
|
137
139
|
when /\AAUTHENTICATE \+\z/
|
138
140
|
logger.warn "password"
|
139
|
-
next socket.write "AUTHENTICATE #{Base64.strict_encode64 "\0#{identity || bot_name}\0#{password}"}"
|
141
|
+
next socket.write "AUTHENTICATE #{::Base64.strict_encode64 "\0#{identity || bot_name}\0#{password}"}"
|
140
142
|
when /\A:[a-z]+\.libera\.chat 903 #{bot_name} :SASL authentication successful\z/
|
141
143
|
next socket.log "CAP END"
|
142
144
|
|
143
145
|
when /\APING :/
|
144
146
|
next socket.write "PONG :#{$'}" # Quakenet uses timestamp, Freenode and Twitch use server name
|
145
|
-
when /\A:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\x01VERSION\x01\z/
|
147
|
+
when /\A:([^!]+)!\S+ PRIVMSG #{::Regexp.escape bot_name} :\x01VERSION\x01\z/
|
146
148
|
next socket.log "NOTICE #{$1} :\x01VERSION name 0.0.0\x01"
|
147
149
|
# when /^:([^!]+)!\S+ PRIVMSG #{Regexp.escape bot_name} :\001PING (\d+)\001$/
|
148
150
|
# socket_log.call "NOTICE",$1,"\001PING #{rand 10000000000}\001"
|
@@ -152,20 +154,23 @@ module NakiIRCBot
|
|
152
154
|
|
153
155
|
begin
|
154
156
|
yield str,
|
155
|
-
->(where, what){
|
157
|
+
->(where, what){ Queue.push [where, what] },
|
156
158
|
->(new_password){ password.replace new_password; socket.instance_variable_set :@socket, nil },
|
157
159
|
*/\A#{'\S+ ' if tags}:(?<who>[^!]+)!\S+ PRIVMSG (?<where>\S+) :(?<what>.+)/.match(str).to_a.drop(1).tap{ |who, where, what|
|
158
160
|
logger.warn "#{where} <#{who}> #{what}" if what
|
159
161
|
}
|
160
162
|
rescue
|
161
163
|
puts $!.full_message
|
162
|
-
|
164
|
+
Queue.push ["##{bot_name}", "error: #{$!}, #{$!.backtrace.first}"]
|
163
165
|
end
|
164
166
|
|
165
|
-
rescue
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
+
|
169
174
|
end
|
170
175
|
|
171
176
|
end
|
@@ -210,13 +215,16 @@ module NakiIRCBot
|
|
210
215
|
"< :tmi.twitch.tv NOTICE * :Improperly formatted auth",
|
211
216
|
"< :tmi.twitch.tv RECONNECT"
|
212
217
|
when /\A< (\S+) :tmi\.twitch\.tv USERSTATE ##{bot_name}\z/ # wtf?
|
218
|
+
when /\Aexception: /
|
213
219
|
when "reconnect",
|
220
|
+
"socket: reconnecting",
|
221
|
+
/\Asocket: exception: /,
|
214
222
|
"< :tmi.twitch.tv 001 #{bot_name} :Welcome, GLHF!"
|
215
223
|
[nil, "RECONNECT"]
|
216
224
|
when /\A< :([^\s!]+)!\1@\1\.tmi\.twitch\.tv (JOIN|PART) #([a-z\d_]+)\z/
|
217
225
|
[$3, $2, $1]
|
218
|
-
when /\A
|
219
|
-
[$
|
226
|
+
when /\A#([a-z\d_]+) <(\S+)> (.+)\z/
|
227
|
+
[$1, "PRIVMSG", $2, $3]
|
220
228
|
when /\A< (\S+) :tmi\.twitch\.tv CLEARMSG #([a-z\d_]+) :((?:\S.*)?\S)\z/
|
221
229
|
[$2, "CLEARMSG", get_tags[$1].fetch("login"), $3]
|
222
230
|
when /\A< (\S+) :tmi\.twitch\.tv CLEARCHAT #([a-z\d_]+) :([^\s!]+)\z/
|
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.1.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-
|
11
|
+
date: 2023-07-18 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.4.13
|
43
43
|
signing_key:
|
44
44
|
specification_version: 4
|
45
45
|
summary: IRC bot framework
|