protonbot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +38 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/CONFIG.md +45 -0
  6. data/CORE_PLUGIN.md +128 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE.txt +21 -0
  9. data/PLUGIN.md +45 -0
  10. data/README.md +81 -0
  11. data/Rakefile +2 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/protonbot-gencert +7 -0
  15. data/lib/protonbot/bot.rb +64 -0
  16. data/lib/protonbot/bot_conf.rb +48 -0
  17. data/lib/protonbot/bot_plugins.rb +110 -0
  18. data/lib/protonbot/core_plugin/apis/help.rb +54 -0
  19. data/lib/protonbot/core_plugin/apis/index.rb +2 -0
  20. data/lib/protonbot/core_plugin/apis/perms.rb +118 -0
  21. data/lib/protonbot/core_plugin/codes/index.rb +3 -0
  22. data/lib/protonbot/core_plugin/codes/names.rb +21 -0
  23. data/lib/protonbot/core_plugin/codes/welcome.rb +7 -0
  24. data/lib/protonbot/core_plugin/codes/whois.rb +9 -0
  25. data/lib/protonbot/core_plugin/commands/admin.rb +107 -0
  26. data/lib/protonbot/core_plugin/commands/basic.rb +9 -0
  27. data/lib/protonbot/core_plugin/commands/cross.rb +40 -0
  28. data/lib/protonbot/core_plugin/commands/help.rb +25 -0
  29. data/lib/protonbot/core_plugin/commands/index.rb +6 -0
  30. data/lib/protonbot/core_plugin/commands/joinpart.rb +25 -0
  31. data/lib/protonbot/core_plugin/commands/perms.rb +50 -0
  32. data/lib/protonbot/core_plugin/hooks/ctcp.rb +9 -0
  33. data/lib/protonbot/core_plugin/hooks/index.rb +7 -0
  34. data/lib/protonbot/core_plugin/hooks/joinpart.rb +42 -0
  35. data/lib/protonbot/core_plugin/hooks/kick.rb +17 -0
  36. data/lib/protonbot/core_plugin/hooks/nick.rb +10 -0
  37. data/lib/protonbot/core_plugin/hooks/ping.rb +3 -0
  38. data/lib/protonbot/core_plugin/hooks/privmsg.rb +62 -0
  39. data/lib/protonbot/core_plugin/hooks/raw.rb +32 -0
  40. data/lib/protonbot/core_plugin/plugin.rb +10 -0
  41. data/lib/protonbot/event_lock.rb +25 -0
  42. data/lib/protonbot/hook.rb +22 -0
  43. data/lib/protonbot/log.rb +182 -0
  44. data/lib/protonbot/log_wrapper.rb +52 -0
  45. data/lib/protonbot/numeric.rb +140 -0
  46. data/lib/protonbot/plug.rb +118 -0
  47. data/lib/protonbot/plug_events.rb +51 -0
  48. data/lib/protonbot/plug_io.rb +99 -0
  49. data/lib/protonbot/plug_utils.rb +242 -0
  50. data/lib/protonbot/plugin.rb +101 -0
  51. data/lib/protonbot/version.rb +4 -0
  52. data/lib/protonbot.rb +38 -0
  53. data/protonbot.gemspec +31 -0
  54. metadata +181 -0
@@ -0,0 +1,51 @@
1
+ class ProtonBot::Plug
2
+ # Emits passed event - calls first matching hook from each plugin
3
+ # @param dat [Hash] Event hash
4
+ # @return [Plug] self
5
+ def emit(dat = {})
6
+ bot.plugins.each do |_, p|
7
+ worked = []
8
+ p.hooks.each do |h|
9
+ next unless dat >= h.pattern && !worked.include?(h.object_id) && worked.empty?
10
+ canrun = true
11
+ h.chain.each do |l|
12
+ next unless canrun
13
+ canrun = l.call(dat, h)
14
+ end
15
+ worked << h.object_id
16
+ h.block.call(dat) if canrun
17
+ end
18
+ worked = []
19
+ end
20
+ event_locks.each_with_index do |el, k|
21
+ if dat >= el.pattern
22
+ event_locks.delete_at(k)
23
+ el.unlock
24
+ end
25
+ end
26
+ self
27
+ end
28
+
29
+ # Emits passed event in new thread
30
+ # @param dat [Hash] Event hash
31
+ # @return [Plug] self
32
+ def emitt(dat = {})
33
+ d = dat.clone
34
+ Thread.new do
35
+ begin
36
+ emit(d)
37
+ rescue => e
38
+ log_err(e)
39
+ end
40
+ end
41
+ self
42
+ end
43
+
44
+ # Creates EventLock with given pattern.
45
+ # @param pattern [Hash]
46
+ # @return [Plug] self
47
+ def wait_for(pattern)
48
+ ProtonBot::EventLock.new(self, pattern)
49
+ self
50
+ end
51
+ end
@@ -0,0 +1,99 @@
1
+ class ProtonBot::Plug
2
+ # Main read-loop. Reads data from socket and emits event `raw`
3
+ def readloop
4
+ while @running
5
+ if s = @sock.gets
6
+ s = s.force_encoding(@conf['encoding'])
7
+ s = s[0..-3]
8
+ log.info "R > #{s}"
9
+ begin
10
+ emit(type: :raw, raw_data: s.clone, plug: self, db: db, bot: bot, core: bot.plugins['core'])
11
+ rescue => e
12
+ log_err(e)
13
+ end
14
+ else
15
+ @running = false
16
+ end
17
+ end
18
+ log.info 'Connection closed.'
19
+ end
20
+
21
+ # Main write-loop. Reads data from queue and sends it to server
22
+ def writeloop
23
+ while @running || !@wqueue.empty?
24
+ s = @queue.pop
25
+ write_(s)
26
+ sleep(@conf['queue_delay'])
27
+ end
28
+ end
29
+
30
+ # Adds given message to the queue
31
+ # @param s [String] Message
32
+ # @return [Plug] self
33
+ def write(s)
34
+ @queue << s
35
+ self
36
+ end
37
+
38
+ # Sends message to server without using queue.
39
+ # @param s [String] Message
40
+ # @return [Plug] self
41
+ def write_(s)
42
+ s = s.encode(@conf['encoding'], s.encoding)
43
+ @sock.puts s
44
+ log.info "W > #{s}"
45
+ end
46
+
47
+ # Sends credentials to server (PASS, NICK, USER).
48
+ def introduce
49
+ write_("PASS #{@conf['pass']}") if @conf['pass']
50
+ write_("NICK #{@conf['nick']}")
51
+ write_("USER #{@conf['user']} 0 * :#{@conf['rnam']}")
52
+ end
53
+
54
+ # Splits given string each 399 bytes and on newlines
55
+ # @param string [String]
56
+ # @return [String] Output
57
+ def self.ssplit(string)
58
+ out = []
59
+ arr = string.split("\n\r")
60
+ arr.each do |i|
61
+ items = i.scan(/.{,399}/)
62
+ items.delete('')
63
+ items.each do |i2|
64
+ out << i2
65
+ end
66
+ end
67
+ out
68
+ end
69
+
70
+ # Colorizes given string.
71
+ # @param string [String]
72
+ # @return [String] Output
73
+ def self.process_colors(string)
74
+ string
75
+ .gsub("%C%", "%C?")
76
+ .gsub(",%", ",?")
77
+ .gsub("%C", "\x03")
78
+ .gsub("%B", "\x02")
79
+ .gsub("%I", "\x10")
80
+ .gsub("%U", "\x1F")
81
+ .gsub("%N", "\x0F")
82
+ .gsub("?WHITE", "0")
83
+ .gsub("?BLACK", "1")
84
+ .gsub("?BLUE", "2")
85
+ .gsub("?GREEN", "3")
86
+ .gsub("?RED", "4")
87
+ .gsub("?BROWN", "5")
88
+ .gsub("?PURPLE", "6")
89
+ .gsub("?ORANGE", "7")
90
+ .gsub("?YELLOW", "8")
91
+ .gsub("?LGREEN", "9")
92
+ .gsub("?CYAN" , "10")
93
+ .gsub("?LCYAN", "11")
94
+ .gsub("?LBLUE", "12")
95
+ .gsub("?PINK", "13")
96
+ .gsub("?GREY", "14")
97
+ .gsub("?LGREY", "15")
98
+ end
99
+ end
@@ -0,0 +1,242 @@
1
+ class ProtonBot::Plug
2
+ # Sends WHOIS message to the server
3
+ # @param nick [String]
4
+ # @return [Plug] self
5
+ def whois(nick)
6
+ write("WHOIS #{nick}")
7
+ self
8
+ end
9
+
10
+ # Gets hostname for given nickname using WHOIS and event lock
11
+ # @param nick [String]
12
+ # @return [String] host
13
+ def gethost(nick)
14
+ if @users[nick] and @users[nick][:host]
15
+ @users[nick][:host]
16
+ else
17
+ Thread.new do
18
+ sleep(1)
19
+ whois(nick)
20
+ end
21
+ wait_for(type: :code_whoisuser, nick: nick)
22
+ end
23
+ @users[nick][:host]
24
+ end
25
+
26
+ # Gets username for given nickname using WHOIS and event lock
27
+ # @param nick [String]
28
+ # @return [String] user
29
+ def getuser(nick)
30
+ if @users[nick] and @users[nick][:user]
31
+ @users[nick][:user]
32
+ else
33
+ Thread.new do
34
+ sleep(1)
35
+ whois(nick)
36
+ end
37
+ wait_for(type: :code, code: ProtonBot::Numeric::WHOISUSER)
38
+ @users[nick][:user]
39
+ end
40
+ end
41
+
42
+ # Changes current nick to given. If successful, `unick` event will be emitted
43
+ # @param to [String]
44
+ # @return [Plug] self
45
+ def change_nick(to)
46
+ write("NICK #{to}")
47
+ self
48
+ end
49
+
50
+ # Joins given channel and uses password if it's provided
51
+ # @param chan [String]
52
+ # @param pass [String] Password
53
+ # @return [Plug] self
54
+ def join(chan, pass = '')
55
+ write("JOIN #{chan} #{pass}")
56
+ self
57
+ end
58
+
59
+ # Parts given channel with given reason
60
+ # @param chan [String]
61
+ # @param reason [String]
62
+ def part(chan, reason = 'Parting...')
63
+ write("PART #{chan} :#{reason}")
64
+ self
65
+ end
66
+
67
+ # Sends message to given target. Splits it each 399 bytes.
68
+ # @param target [String]
69
+ # @param msg [String]
70
+ # @return [Plug] self
71
+ def privmsg(target, msg)
72
+ self.class.ssplit(self.class.process_colors(msg)).each do |m|
73
+ write("PRIVMSG #{target} :\u200B#{m}")
74
+ end
75
+ self
76
+ end
77
+
78
+ # Sends notice to given target. Splits it each 399 bytes.
79
+ # @param target [String]
80
+ # @param msg [String]
81
+ # @return [Plug] self
82
+ def notice(target, msg)
83
+ self.class.ssplit(self.class.process_colors(msg)).each do |m|
84
+ write("NOTICE #{target} :\u200B#{m}")
85
+ end
86
+ self
87
+ end
88
+
89
+ # Sends CTCP to given target.
90
+ # @param target [String]
91
+ # @param msg [String]
92
+ # @return [Plug] self
93
+ def ctcp(target, msg)
94
+ write("PRIVMSG #{target} :\x01#{msg}\x01")
95
+ self
96
+ end
97
+
98
+ # Sends NCTCP to given target.
99
+ # @param target [String]
100
+ # @param msg [String]
101
+ # @return [Plug] self
102
+ def nctcp(target, msg)
103
+ write("NOTICE #{target} :\x01#{msg}\x01")
104
+ self
105
+ end
106
+
107
+ # Invites user to given channel
108
+ # @param chan [String]
109
+ # @param nick [String]
110
+ # @return [Plug] self
111
+ def invite(chan, nick)
112
+ write("INVITE #{nick} #{chan}")
113
+ self
114
+ end
115
+
116
+ # Sets mode on given user at given channel
117
+ # @param chan [String]
118
+ # @param nick [String]
119
+ # @param mode [String]
120
+ # @return [Plug] self
121
+ def usermode(chan, nick, mode)
122
+ write("MODE #{chan} #{mode} #{nick}")
123
+ self
124
+ end
125
+
126
+ # Sets mode on given channel
127
+ # @param chan [String]
128
+ # @param mode [String]
129
+ # @return [Plug] self
130
+ def chanmode(chan, mode)
131
+ write("MODE #{chan} #{mode}")
132
+ self
133
+ end
134
+
135
+ # Sets quiet on given user at given channel
136
+ # @param chan [String]
137
+ # @param nick [String]
138
+ # @return [Plug] self
139
+ def quiet(chan, nick)
140
+ usermode(chan, nick, '+q')
141
+ self
142
+ end
143
+
144
+ # Removes quiet on given user at given channel
145
+ # @param chan [String]
146
+ # @param nick [String]
147
+ # @return [Plug] self
148
+ def unquiet(chan, nick)
149
+ usermode(chan, nick, '-q')
150
+ self
151
+ end
152
+
153
+ # Sets ban on given user at given channel
154
+ # @param chan [String]
155
+ # @param nick [String]
156
+ # @return [Plug] self
157
+ def ban(chan, nick)
158
+ usermode(chan, nick, '+b')
159
+ self
160
+ end
161
+
162
+ # Removes ban on given user at given channel
163
+ # @param chan [String]
164
+ # @param nick [String]
165
+ # @return [Plug] self
166
+ def unban(chan, nick)
167
+ usermode(chan, nick, '-b')
168
+ self
169
+ end
170
+
171
+ # Sets excempt on given user at given channel
172
+ # @param chan [String]
173
+ # @param nick [String]
174
+ # @return [Plug] self
175
+ def excempt(chan, nick)
176
+ usermode(chan, nick, '+e')
177
+ self
178
+ end
179
+
180
+ # Removes excempt on given user at given channel
181
+ # @param chan [String]
182
+ # @param nick [String]
183
+ # @return [Plug] self
184
+ def unexcempt(chan, nick)
185
+ usermode(chan, nick, '-e')
186
+ self
187
+ end
188
+
189
+ # Ops given user at given channel
190
+ # @param chan [String]
191
+ # @param nick [String]
192
+ # @return [Plug] self
193
+ def op(chan, nick)
194
+ usermode(chan, nick, '+o')
195
+ self
196
+ end
197
+
198
+ # Deops given user at given channel
199
+ # @param chan [String]
200
+ # @param nick [String]
201
+ # @return [Plug] self
202
+ def deop(chan, nick)
203
+ usermode(chan, nick, '-o')
204
+ self
205
+ end
206
+
207
+ # Voices given user at given channel
208
+ # @param chan [String]
209
+ # @param nick [String]
210
+ # @return [Plug] self
211
+ def voice(chan, nick)
212
+ usermode(chan, nick, '+v')
213
+ self
214
+ end
215
+
216
+ # Devoices on given user at given channel
217
+ # @param chan [String]
218
+ # @param nick [String]
219
+ # @return [Plug] self
220
+ def devoice(chan, nick)
221
+ usermode(chan, nick, '-v')
222
+ self
223
+ end
224
+
225
+ # Kicks given user from given channel
226
+ # @param chan [String]
227
+ # @param nick [String]
228
+ # @return [Plug] self
229
+ def kick(chan, nick, reason = 'Default Kick Reason')
230
+ write("KICK #{chan} #{nick} :#{reason}")
231
+ self
232
+ end
233
+
234
+ # Removes given user from given channel
235
+ # @param chan [String]
236
+ # @param nick [String]
237
+ # @return [Plug] self
238
+ def remove(chan, nick, reason = 'Default Remove Reason')
239
+ write("REMOVE #{chan} #{nick} :#{reason}")
240
+ self
241
+ end
242
+ end
@@ -0,0 +1,101 @@
1
+ # Plugin object. Use it to alter bot's behavior
2
+ # @see http://www.rubydoc.info/gems/protonbot/file/plugin-demo.md
3
+ # @!attribute [r] name
4
+ # @return [String] Plugin's name
5
+ # @!attribute [r] version
6
+ # @return [String] Plugin's version
7
+ # @!attribute [r] description
8
+ # @return [String] Plugin's description
9
+ # @!attribute [r] name
10
+ # @return [String] Plugin's name
11
+ # @!attribute [r] hooks
12
+ # @return [Array<Hook>] Plugin's hooks
13
+ # @!attribute [rw] bot
14
+ # @return [Bot] Bot
15
+ # @!attribute [rw] Log
16
+ # @return [LogWrapper] Log
17
+ # @!attribute [rw] path
18
+ # @return [String] Path
19
+ # @!attribute [r] core
20
+ # @return [Plugin] Core plugin
21
+ class ProtonBot::Plugin
22
+ attr_reader :name, :version, :description, :plugins, :hooks, :core
23
+ attr_accessor :bot, :log, :path
24
+
25
+ # @param block [Proc]
26
+ def initialize(&block)
27
+ @hooks = []
28
+ @block = block
29
+ @numeric = ProtonBot::Numeric
30
+ end
31
+
32
+ # Starts plugin
33
+ # @return [Plugin] self
34
+ def launch
35
+ @plugins = @bot.plugins
36
+ @core = @bot.plugins['core']
37
+ instance_exec(&@block)
38
+ raise ProtonBot::PluginError, 'Plugin-name is not set!' unless
39
+ @name
40
+ raise ProtonBot::PluginError, 'Plugin-description is not set!' unless
41
+ @description
42
+ raise ProtonBot::PluginError, 'Plugin-description is not set!' unless
43
+ @description
44
+ log.info("Started plugin `#{@name} v#{@version}` successfully!")
45
+ end
46
+
47
+ # Creates hook and returns it
48
+ # @param pattern [Hash<Symbol>]
49
+ # @param block [Proc]
50
+ # @return [Hook] hook
51
+ def hook(pattern = {}, &block)
52
+ h = ProtonBot::Hook.new(pattern, &block)
53
+ @hooks << h
54
+ h
55
+ end
56
+
57
+ # Shortcut for `hook(type: :command, ...) {}`
58
+ # @param pattern [Hash<Symbol>]
59
+ # @param block [Proc]
60
+ # @return [Hook] hook
61
+ def cmd(pattern = {}, &block)
62
+ hook(pattern.merge(type: :command), &block)
63
+ end
64
+
65
+ # Emits passed event
66
+ # @param dat [Hash<Symbol>]
67
+ def emit(dat = {})
68
+ dat[:plug].emit(dat) if dat[:plug]
69
+ end
70
+
71
+ # Emits passed event in other thread
72
+ # @param dat [Hash<Symbol>]
73
+ def emitt(dat = {})
74
+ dat[:plug].emitt(dat) if dat[:plug]
75
+ end
76
+
77
+ # Runs given file (.rb is appended automatically)
78
+ # @param fname [String]
79
+ def run(fname)
80
+ if @name == 'Core'
81
+ instance_eval File.read("#{Gem.loaded_specs['protonbot'].lib_dirs_glob}/" \
82
+ "protonbot/core_plugin/#{fname}.rb"), "#{name}/#{fname}.rb"
83
+ else
84
+ instance_exec do
85
+ instance_eval File.read(File.expand_path("#{path}/#{fname}.rb")), "#{name}/#{fname}.rb"
86
+ end
87
+ end
88
+ end
89
+
90
+ # Shortcut for `define_singleton_method(...)`
91
+ # @param name [String,Symbol]
92
+ # @param block [Proc]
93
+ def fun(name, &block)
94
+ define_singleton_method(name, &block)
95
+ end
96
+
97
+ # @return [String] Output
98
+ def inspect
99
+ %(<#ProtonBot::Plugin:#{object_id.to_s(16)} @name=#{name} @version=#{version}>)
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ module ProtonBot
2
+ # ProtonBot's version
3
+ VERSION = '0.1.0'.freeze
4
+ end
data/lib/protonbot.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'English'
2
+
3
+ require 'date'
4
+
5
+ require 'pastel'
6
+ require 'tty'
7
+ require 'irb'
8
+
9
+ require 'heliodor'
10
+
11
+ require 'open3'
12
+
13
+ require 'socket'
14
+ require 'openssl'
15
+
16
+ module ProtonBot
17
+ end
18
+
19
+ require 'protonbot/version'
20
+
21
+ require 'protonbot/numeric'
22
+
23
+ require 'protonbot/log'
24
+ require 'protonbot/log_wrapper'
25
+
26
+ require 'protonbot/bot'
27
+ require 'protonbot/bot_conf'
28
+ require 'protonbot/bot_plugins'
29
+
30
+ require 'protonbot/event_lock'
31
+
32
+ require 'protonbot/plug'
33
+ require 'protonbot/plug_io'
34
+ require 'protonbot/plug_utils'
35
+ require 'protonbot/plug_events'
36
+
37
+ require 'protonbot/plugin'
38
+ require 'protonbot/hook'
data/protonbot.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'protonbot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'protonbot'
8
+ spec.version = ProtonBot::VERSION
9
+ spec.authors = ['Nickolay']
10
+ spec.email = ['nickolay02@inbox.ru']
11
+
12
+ spec.summary = 'An IRC bot library'
13
+ spec.description = 'Library for writing cross-server IRC bots'
14
+ spec.homepage = 'https://github.com/handicraftsman/ProtonBot'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.13'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rubocop', '~> 0.47'
27
+
28
+ spec.add_runtime_dependency 'pastel', '~> 0.7.1'
29
+ spec.add_runtime_dependency 'tty', '~> 0.6.1'
30
+ spec.add_runtime_dependency 'heliodor', '~> 0.2.0'
31
+ end