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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nakiircbot.rb +72 -64
  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: 96c17ebf4d920d49e111bcc3f2d5eae3cbf5dd067df1945e32e85b088d49be84
4
- data.tar.gz: a77837bbcd9a0d7ecc1904afa56864e232ae7d25d391e0b74a79d38bbc4a8926
3
+ metadata.gz: 0c070c5f505213c2ca011f3a87911603f2b327c2787406c5ffe2b9e8fdc60495
4
+ data.tar.gz: 145078acb492b0c2afb570be54145eeaca0fa749978b1dcbce59470ac7f84a74
5
5
  SHA512:
6
- metadata.gz: 744091bd4b6fa48763d2f762536c012ac743997afa1705f378b77bb49cb87c64e55a05408d2c76ebb3b3a705a56127625e45d8103280a072b98cfa1fd8603438
7
- data.tar.gz: 70f8088b001c1b23d407d47fdcbf73b4ac4d37ba230ae7b80b59a09332c4aa518c89eb5b34278d5469bd505b843426026e763aca61c8bebdc48b995cb10f1fec
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
- @queue = Queue.new
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 = ENV["LOGLEVEL_#{name}"].to_sym if ENV.include? "LOGLEVEL_#{name}"
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
- # require "io/wait"
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
- def self.rescue_socket
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
- @socket ||= rescue_socket do
45
- @logger.warn "reconnect"
46
- TCPSocket.new(@server, @port)#.tap{ queue_thread.run }
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( rescue_socket do
54
- return unless select [socket], nil, nil, 1
55
- @socket.read(@socket.nread).tap{ |_| raise SocketError if _.empty? }
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 = @queue.pop
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
- # socket_log.call "CAP LS"
86
- # https://ircv3.net/specs/extensions/sasl-3.1.html
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
- break if Time.now - prev_socket_time > 300
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
- next socket.log "JOIN #{channels.join ","}"
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){ @queue.push [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
- @queue.push ["##{bot_name}", "error: #{$!}, #{$!.backtrace.first}"]
164
+ Queue.push ["##{bot_name}", "error: #{$!}, #{$!.backtrace.first}"]
163
165
  end
164
166
 
165
- rescue
166
- puts $!.full_message
167
- sleep 5
168
- raise
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< (?:\S+ )?:([^\s!]+)!\1@\1\.tmi\.twitch\.tv PRIVMSG #([a-z\d_]+) :((?:\S.*)?\S)\z/
219
- [$2, "PRIVMSG", $1, $3]
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "nakiircbot"
3
- spec.version = "1.0.0"
3
+ spec.version = "1.1.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.0.0
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-05-07 00:00:00.000000000 Z
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.1.4
42
+ rubygems_version: 3.4.13
43
43
  signing_key:
44
44
  specification_version: 4
45
45
  summary: IRC bot framework