imouto 1.0.6

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.
@@ -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: []