imouto 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/imouto.rb +99 -0
- data/lib/irc.rb +111 -0
- data/lib/irc_commands.rb +50 -0
- data/lib/ratelimited_queue.rb +35 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/imouto.rb
ADDED
@@ -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
|
data/lib/irc.rb
ADDED
@@ -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
|
data/lib/irc_commands.rb
ADDED
@@ -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: []
|