imouto 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40af6dcddca1548d72857fde8fc7fef2f492f764
4
+ data.tar.gz: b01aef77249fdb1da5f6c92cf0f050450a575f19
5
+ SHA512:
6
+ metadata.gz: c2225f2f2ae6773882ce15e17f8f21e58eb1242f45d65bbb36d98934f35cf103312c2a9bb077ff4792128deecbfb6d3705640c1a34904c8a9ecae47906acfffa
7
+ data.tar.gz: 9c177a5422eb77ac78a42d5270144309380fd4840bf06eb1775db5ecf76fb149aeccc07ef7d40536c0b29671a33dda5b0ae4612d48281fe63ac6e4097199bbcf
@@ -0,0 +1,99 @@
1
+ require_relative 'irc'
2
+ require_relative 'ratelimited_queue'
3
+
4
+ module Imouto
5
+
6
+ # IRC-PRIVMSG, passed to matchers.
7
+ Message = Struct.new(
8
+ # PRIVMSG as string.
9
+ :message,
10
+ # Captures specified in matcher-regex
11
+ :captures,
12
+ # Raw MatchData
13
+ :raw
14
+ )
15
+
16
+ ImoutoConfig = Struct.new(
17
+ :loggers,
18
+ :message_interval_size,
19
+ :messages_per_interval
20
+ )
21
+
22
+ class Bot
23
+ attr_reader :matchers, :irc, :reply_queue
24
+
25
+ def initialize(irc, conf)
26
+ imouto_config = ImoutoConfig.new
27
+ conf.each { |k, v| imouto_config[k] = v; }
28
+ @loggers = imouto_config.loggers || [->(msg) { p msg }]
29
+ @matchers = {}
30
+ @irc = irc
31
+ @reply_queue = Imouto::RatelimitedQueue.new(
32
+ imouto_config.messages_per_interval || 3,
33
+ imouto_config.message_interval_size || 4)
34
+ end
35
+
36
+ # Starts the bot, spawning a read- and a write-thread
37
+ def start
38
+ read_thread = Thread.new { read(@irc) }
39
+ write_thread = Thread.new { dequeue_replies }
40
+ read_thread.join
41
+ write_thread.join
42
+ end
43
+
44
+ # Read from the IRC-connection until it closes.
45
+ def read(irc_con = @irc)
46
+ irc_con.start.read { |msg, matchers = @matchers|
47
+ matchers.keys.each { |regex|
48
+ msg[:message] =~ regex && reply(msg, matchers[regex], regex.match(msg[:message]))
49
+ }
50
+ }
51
+ end
52
+
53
+ # Reply to a PRIVMSG
54
+ def reply(msg, matcher, matches)
55
+ log("[Executing Matcher] #{msg[:message]}")
56
+ m = Message.new(msg[:message], matches, msg)
57
+ begin
58
+ reply = Thread.new { matcher.call(m) }.value
59
+ rescue StandardError => e
60
+ log("[Matcher Exception] #{e}")
61
+ end
62
+ reply_to = msg[:target].start_with?('#') ? msg[:target] : msg[:nick]
63
+ queue_reply(reply_to, reply)
64
+ end
65
+
66
+ # Write replies to the IRC-connection
67
+ def dequeue_replies(irc_con = @irc)
68
+ @reply_queue.dequeue { |reply, irc = irc_con|
69
+ log("[->] #{reply['target']}: #{reply['reply']}")
70
+ irc.privmsg(reply['target'], reply['reply'])
71
+ }
72
+ end
73
+
74
+ # Enqueues a reply, to be sent.
75
+ # You can also directly send messages by using @irc.privmsg
76
+ # this however bypasses rate limiting and might get you kicked.
77
+ def queue_reply(target, reply)
78
+ @reply_queue.enqueue('target' => target, 'reply' => reply)
79
+ end
80
+
81
+ # Calls every logger with a message.
82
+ def log(msg)
83
+ @loggers.each { |l| l.call(msg) }
84
+ end
85
+
86
+ # Registers a matcher to respond to PRIVMSGs that match a certain regex.
87
+ def register_matcher(regex, matcher)
88
+ return false unless matcher.respond_to? 'call'
89
+ @matchers[regex] = matcher
90
+ log("[Registered Matcher] #{regex}")
91
+ end
92
+
93
+ # Consumes a regex, and removes the matcher that was registered with it.
94
+ def unregister_matcher(regex)
95
+ @matchers.delete(regex)
96
+ log("[Removed Matcher] #{regex}")
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,111 @@
1
+ require 'timeout'
2
+ require 'net/protocol'
3
+ require_relative 'irc_commands'
4
+
5
+ module Imouto
6
+ Connection = Struct.new(
7
+ :server,
8
+ :port,
9
+ :channels,
10
+ :connect_timeout,
11
+ :read_timeout
12
+ )
13
+
14
+ User = Struct.new(
15
+ :nick,
16
+ :username,
17
+ :realname,
18
+ :password
19
+ )
20
+
21
+ class Enum
22
+ attr_reader :options, :selected
23
+
24
+ def initialize(options, selected)
25
+ raise ArgumentError, "Expected argument options to respond to method 'at'" unless options.respond_to? 'at'
26
+ @options = options
27
+ @selected = options.first
28
+ set selected
29
+ end
30
+
31
+ def set(value)
32
+ return @selected unless @options.include? value
33
+ @selected = value
34
+ end
35
+ end
36
+
37
+ class Irc
38
+ include IrcCommands
39
+
40
+ attr_reader :connection, :user, :connection_state
41
+
42
+
43
+ def initialize(connection, user)
44
+ @connection = Connection.new
45
+ connection.each { |k, v| @connection[k] = v; }
46
+ @user = User.new
47
+ user.each { |k, v| @user[k] = v; }
48
+ @connection_state = Enum.new([:not_connected, :connecting, :connected, :failed], :not_connected)
49
+ self
50
+ end
51
+
52
+ # Stuff to do when the connection is successfully established
53
+ def setup
54
+ join @connection.channels
55
+ self
56
+ end
57
+
58
+ # see irc_commands.rb
59
+ def raw(msg)
60
+ @socket.puts msg
61
+ end
62
+
63
+ # Starts an IRC-connection. Chainable,-
64
+ # you probably want to do irc.start.read {|msg| ...}
65
+ def start
66
+ begin
67
+ Timeout.timeout(@connection.connect_timeout) {
68
+ @socket = TCPSocket.new(@connection.server, @connection.port)
69
+ raw "PASS #{@user.password}"
70
+ raw "NICK #{@user.nick}"
71
+ raw "USER #{@user.username} 0 * :#{@user.realname}"
72
+ }
73
+ rescue Timeout::Error
74
+ @connection_state.set(:failed)
75
+ puts 'Timeout! Aborting.'
76
+ return false
77
+ rescue SocketError => e
78
+ @connection_state.set(:failed)
79
+ puts "Network error: #{e}"
80
+ return false
81
+ rescue => e
82
+ @connection_state.set(:failed)
83
+ puts "General exception: #{e}"
84
+ return false
85
+ end
86
+ @connection_state.set(:connecting)
87
+ self
88
+ end
89
+
90
+ # Yields PRIVMSGs and handles PINGs as well as setup operations
91
+ def read
92
+ until @socket.eof?
93
+ msg = @socket.gets
94
+ if msg.start_with? 'PING'
95
+ raw "PONG #{msg[6..-1]}"
96
+ next
97
+ end
98
+ if msg.include? 'PRIVMSG'
99
+ m = msg.chomp.match(/:(?<nick>.*)!(?<mask>.*) PRIVMSG (?<target>.*) :(?<message>.*)$/)
100
+ yield m
101
+ end
102
+ if (@connection_state.selected == :connecting) && (msg =~ /:(.*) 376 #{@user.nick} :(.*)$/)
103
+ setup
104
+ @connection_state.set(:connected)
105
+ next
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,50 @@
1
+ module IrcCommands
2
+ # Overwrite raw() to send to your socket wherever you include this
3
+ def raw(string)
4
+ puts string
5
+ end
6
+
7
+ def mode(nick, state, mode)
8
+ return false unless %w(a i w r o O s).include? mode
9
+ raw "MODE #{nick} #{state}#{mode}"
10
+ end
11
+
12
+ def quit(msg = '')
13
+ raw "QUIT :#{msg}"
14
+ end
15
+
16
+ def join(channels)
17
+ channels.respond_to?('join') && channels = channels.join(',')
18
+ raw "JOIN #{channels}"
19
+ end
20
+
21
+ def part(channels, msg = '')
22
+ channels.respond_to?('join') && channels = channels.join(',')
23
+ raw "PART #{channels} :#{msg}"
24
+ end
25
+
26
+ def channel_mode(channel, state, mode, modeparams = '')
27
+ return false unless %w(O o v a i m n q p s r t k l b e I).include? mode
28
+ raw "MODE #{channel} #{state} #{mode} #{modeparams}"
29
+ end
30
+
31
+ def topic(channel, topic)
32
+ raw "TOPIC #{channel} :#{topic}"
33
+ end
34
+
35
+ def invite(nick, channel)
36
+ raw "INVITE #{nick} #{channel}"
37
+ end
38
+
39
+ def kick(channel, user, reason = '')
40
+ raw "KICK #{channel} #{user} :#{reason}"
41
+ end
42
+
43
+ def privmsg(target, msg)
44
+ raw "PRIVMSG #{target} :#{msg}"
45
+ end
46
+
47
+ def notice(target, msg)
48
+ raw "NOTICE #{target} :#{msg}"
49
+ end
50
+ end
@@ -0,0 +1,35 @@
1
+ module Imouto
2
+ class RatelimitedQueue
3
+ attr_reader :messages_per_interval, :interval_in_seconds
4
+
5
+ def initialize(messages_per_interval, interval_in_seconds)
6
+ @messages_per_interval = messages_per_interval
7
+ @interval_in_seconds = interval_in_seconds
8
+ @messages_this_interval = 0
9
+ @current_interval = Time.now
10
+ @items = []
11
+ end
12
+
13
+ def enqueue(item)
14
+ @items << item
15
+ end
16
+
17
+ def dequeue
18
+ loop {
19
+ if @items.empty?
20
+ sleep 1
21
+ next
22
+ end
23
+ interval = Time.now
24
+ if interval > @current_interval + @interval_in_seconds
25
+ @current_interval = interval
26
+ @messages_this_interval = 0
27
+ end
28
+ if @messages_this_interval < @messages_per_interval
29
+ @messages_this_interval += 1
30
+ yield @items.shift
31
+ end
32
+ }
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imouto
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.6
5
+ platform: ruby
6
+ authors:
7
+ - zwwwdr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A really basic & lightweight IRC-client-lib. Mix with a few lambdas for
14
+ instant bot.
15
+ email: zwdr@cock.li
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/imouto.rb
21
+ - lib/irc.rb
22
+ - lib/irc_commands.rb
23
+ - lib/ratelimited_queue.rb
24
+ homepage: https://github.com/2-3/imouto
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.4.5.1
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Basic IRC bot library, without frills
48
+ test_files: []