irc_machine 0.0.1
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.
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +2 -0
- data/README.md +56 -0
- data/Rakefile +1 -0
- data/bin/irc_machine +6 -0
- data/bin/irc_machined +26 -0
- data/example.json +13 -0
- data/irc_machine.gemspec +27 -0
- data/lib/irc_machine.rb +21 -0
- data/lib/irc_machine/cli_shared.rb +6 -0
- data/lib/irc_machine/commands.rb +38 -0
- data/lib/irc_machine/controller/channels_controller.rb +32 -0
- data/lib/irc_machine/controller/github_notifications_controller.rb +12 -0
- data/lib/irc_machine/core.rb +54 -0
- data/lib/irc_machine/core_routes.rb +16 -0
- data/lib/irc_machine/http_controller.rb +35 -0
- data/lib/irc_machine/http_router.rb +66 -0
- data/lib/irc_machine/http_server.rb +74 -0
- data/lib/irc_machine/irc_connection.rb +50 -0
- data/lib/irc_machine/monkey_patches.rb +13 -0
- data/lib/irc_machine/plugin.rb +4 -0
- data/lib/irc_machine/plugin/base.rb +10 -0
- data/lib/irc_machine/plugin/github_notifier.rb +58 -0
- data/lib/irc_machine/plugin/hello.rb +7 -0
- data/lib/irc_machine/plugin/reloader.rb +33 -0
- data/lib/irc_machine/session.rb +97 -0
- data/lib/irc_machine/state.rb +21 -0
- data/lib/irc_machine/udp_server.rb +20 -0
- data/lib/irc_machine/version.rb +3 -0
- metadata +144 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm @irc_machine --create
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
IRC Machine
|
2
|
+
===========
|
3
|
+
|
4
|
+
An IRC bot with a RESTful HTTP interface, built on Ruby and [EventMachine](http://rubyeventmachine.com/).
|
5
|
+
|
6
|
+
Design philosophy: simple to the point of under-engineered, make it work for the 90% case.
|
7
|
+
|
8
|
+
# something like this might work
|
9
|
+
git clone git://github.com/pda/irc_machine
|
10
|
+
cd irc_machine
|
11
|
+
cp example.json irc_machine.json
|
12
|
+
|
13
|
+
# run it
|
14
|
+
./bin/irc_machined run
|
15
|
+
# ctrl+c
|
16
|
+
|
17
|
+
# daemonize it
|
18
|
+
./bin/irc_machined start
|
19
|
+
# stop the daemon
|
20
|
+
./bin/irc_machined stop
|
21
|
+
|
22
|
+
# or maybe even this (chances aren't good, though)
|
23
|
+
gem install irc_machine
|
24
|
+
irc_machined run
|
25
|
+
|
26
|
+
|
27
|
+
HTTP interface
|
28
|
+
--------------
|
29
|
+
|
30
|
+
The RESTful HTTP API is provided by `IrcMachine::Plugin::Rest`. It listens on port 8421 by default. And you can't change the default.
|
31
|
+
|
32
|
+
* `GET /channels` returns a JSON list of channels the bot is probably in.
|
33
|
+
* `PUT /channels/{name}` joins a channel.
|
34
|
+
* `DELETE /channels/{name}` parts a channel.
|
35
|
+
* `POST /channels/{name}` sends a text/plain message to a channel, auto-joins if required.
|
36
|
+
* `POST /channels/{name}/github` accepts GitHub post-receive hook notifications, notifies channel.
|
37
|
+
|
38
|
+
|
39
|
+
Plugins
|
40
|
+
-------
|
41
|
+
|
42
|
+
Plugins are objects which might respond to `#start` or `#receive_line`, and might use a reference to the `IrcMachine::Session` instance to send IRC commands.
|
43
|
+
|
44
|
+
|
45
|
+
Contributors
|
46
|
+
------------
|
47
|
+
|
48
|
+
* [Paul Annesley](https://github.com/pda)
|
49
|
+
* [Eric Anderson](https://github.com/ericanderson)
|
50
|
+
* [Anton Lindström](https://github.com/antonlindstrom)
|
51
|
+
|
52
|
+
|
53
|
+
Meh.
|
54
|
+
----
|
55
|
+
|
56
|
+
© Paul Annesley, 2011, [MIT license](http://www.opensource.org/licenses/mit-license.php)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/irc_machine
ADDED
data/bin/irc_machined
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path("../../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'daemons'
|
6
|
+
|
7
|
+
require 'irc_machine/cli_shared'
|
8
|
+
|
9
|
+
daemon_defaults = {
|
10
|
+
:app_name => 'irc_machine',
|
11
|
+
:dir_mode => :normal,
|
12
|
+
:dir => ".",
|
13
|
+
:multiple => false,
|
14
|
+
:backtrace => true,
|
15
|
+
:monitor => false,
|
16
|
+
:log_dir => File.expand_path("../../log", __FILE__),
|
17
|
+
:log_output => true
|
18
|
+
}
|
19
|
+
|
20
|
+
IRC_MACHINE[:daemon] ||= {}
|
21
|
+
|
22
|
+
daemon_params = daemon_defaults.merge(IRC_MACHINE[:daemon])
|
23
|
+
|
24
|
+
FileUtils.mkdir_p(daemon_params[:log_dir])
|
25
|
+
|
26
|
+
Daemons.run(File.expand_path("../irc_machine", __FILE__), daemon_params)
|
data/example.json
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"server": "localhost",
|
3
|
+
"port": 6667,
|
4
|
+
"nick": "im",
|
5
|
+
"user": "irc_machine",
|
6
|
+
"realname": "IRC Machine",
|
7
|
+
"channels": [ "#irc_machine", "#secret the_key" ],
|
8
|
+
"verbose": true,
|
9
|
+
"udp_port": 8421,
|
10
|
+
"http_port": 8421,
|
11
|
+
"bind_address": "127.0.0.1",
|
12
|
+
"ssl": false
|
13
|
+
}
|
data/irc_machine.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "irc_machine/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "irc_machine"
|
7
|
+
s.version = IrcMachine::VERSION
|
8
|
+
s.authors = ["Paul Annesley"]
|
9
|
+
s.email = ["paul@annesley.cc"]
|
10
|
+
s.homepage = "https://github.com/pda/irc_machine"
|
11
|
+
s.summary = %q{irc machine}
|
12
|
+
s.description = %q{An IRC bot using EventMachine, and perhaps ØMQ one day.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency "eventmachine"
|
20
|
+
s.add_dependency "eventmachine_httpserver"
|
21
|
+
s.add_dependency "rack"
|
22
|
+
s.add_dependency "daemons"
|
23
|
+
|
24
|
+
s.add_development_dependency "bundler"
|
25
|
+
s.add_development_dependency "rake"
|
26
|
+
|
27
|
+
end
|
data/lib/irc_machine.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
%w{
|
2
|
+
ostruct
|
3
|
+
eventmachine
|
4
|
+
evma_httpserver
|
5
|
+
evma_httpserver/response
|
6
|
+
rack
|
7
|
+
}.each do |name|
|
8
|
+
require name
|
9
|
+
end
|
10
|
+
|
11
|
+
%w{
|
12
|
+
commands
|
13
|
+
irc_connection
|
14
|
+
session
|
15
|
+
state
|
16
|
+
plugin
|
17
|
+
plugin/base
|
18
|
+
plugin/reloader
|
19
|
+
}.each do |name|
|
20
|
+
require "irc_machine/#{name}"
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
def raw(raw)
|
5
|
+
puts ">> #{raw}"
|
6
|
+
irc_connection.send_data "#{raw}\r\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
def user(user, name)
|
10
|
+
raw "USER #{user} 8 * :#{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def password(password)
|
14
|
+
raw "PASS #{password}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def nick(nick)
|
18
|
+
raw "NICK #{nick}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def join(channel, key = nil)
|
22
|
+
raw "JOIN #{channel}".tap { |c| c << " #{key}" if key }
|
23
|
+
end
|
24
|
+
|
25
|
+
def part(channel)
|
26
|
+
raw "PART #{channel}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def quit(reason)
|
30
|
+
raw "QUIT :#{reason}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def msg(to, text)
|
34
|
+
raw "PRIVMSG #{to} :#{text}"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
module Controller
|
3
|
+
class ChannelsController < HttpController
|
4
|
+
|
5
|
+
def list
|
6
|
+
content_type "application/json"
|
7
|
+
ok session.state.channels.to_json << "\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def join
|
11
|
+
session.join channel(match), request.GET["key"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def part
|
15
|
+
session.part channel(match)
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
input = request.body.gets
|
20
|
+
source = request.env["HTTP_X_AUTH"] || request.ip || "unknown"
|
21
|
+
session.msg channel(match), "[#{source}] #{input.chomp}" if input
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def channel(match)
|
27
|
+
"#" + match[1]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
class Core < Plugin::Base
|
3
|
+
|
4
|
+
def connected
|
5
|
+
session.password options.password unless options.password.nil?
|
6
|
+
session.user options.user, options.realname
|
7
|
+
session.nick options.nick
|
8
|
+
session.state.nick = options.nick
|
9
|
+
EM::add_timer(5) do
|
10
|
+
options.channels.each { |c| session.join *c.split } if options.channels
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def terminate
|
15
|
+
session.quit "shutting down"
|
16
|
+
end
|
17
|
+
|
18
|
+
def receive_line(line)
|
19
|
+
|
20
|
+
puts "[core] << #{line}" if options.verbose
|
21
|
+
|
22
|
+
case line
|
23
|
+
|
24
|
+
when /^PING (.*)/
|
25
|
+
session.raw "PONG #{$1}"
|
26
|
+
|
27
|
+
when /^:#{self_pattern} JOIN :(\S+)/
|
28
|
+
channels << $1
|
29
|
+
|
30
|
+
when /^:#{self_pattern} PART :(\S+)/
|
31
|
+
channels.delete $1
|
32
|
+
|
33
|
+
when /^:\S+ 475 \S+ (\S+) :(.*)$/
|
34
|
+
puts "[core] #{$1}: #{$2}"
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def options
|
42
|
+
session.options
|
43
|
+
end
|
44
|
+
|
45
|
+
def self_pattern
|
46
|
+
Regexp.escape(session.state.nick) + '!\S+@\S+'
|
47
|
+
end
|
48
|
+
|
49
|
+
def channels
|
50
|
+
session.state.channels
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
module CoreRoutes
|
3
|
+
|
4
|
+
CHANNEL_REGEXP ||= %r{^/channels/([\w-]+)$}
|
5
|
+
|
6
|
+
def draw_routes
|
7
|
+
get "/channels", "ChannelsController#list"
|
8
|
+
put CHANNEL_REGEXP, "ChannelsController#join"
|
9
|
+
delete CHANNEL_REGEXP, "ChannelsController#part"
|
10
|
+
post CHANNEL_REGEXP, "ChannelsController#message"
|
11
|
+
|
12
|
+
post %r{^/channels/([\w-]+)/github$}, "GithubNotificationsController#notify"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
class HttpController
|
3
|
+
|
4
|
+
def self.dispatch(session, request, method, match)
|
5
|
+
new(session, request, match).tap do |c|
|
6
|
+
c.send method
|
7
|
+
end.response
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(session, request, match)
|
11
|
+
@session = session
|
12
|
+
@request = request
|
13
|
+
@match = match
|
14
|
+
@response = Rack::Response.new
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :session
|
18
|
+
attr_reader :request, :response
|
19
|
+
attr_reader :match
|
20
|
+
|
21
|
+
def ok(content)
|
22
|
+
@response.status = 200
|
23
|
+
@response.write content
|
24
|
+
end
|
25
|
+
|
26
|
+
def not_found
|
27
|
+
@response.status = 404
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_type(type)
|
31
|
+
@response["Content-Type"] = type
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "evma_httpserver/response"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
module IrcMachine
|
5
|
+
class HttpRouter
|
6
|
+
|
7
|
+
include CoreRoutes
|
8
|
+
|
9
|
+
def initialize(session)
|
10
|
+
@session = session
|
11
|
+
@routes = { get: [], put: [], delete:[], post: [] }
|
12
|
+
draw_routes
|
13
|
+
end
|
14
|
+
|
15
|
+
def route(env)
|
16
|
+
request = Rack::Request.new(env)
|
17
|
+
|
18
|
+
match = lookup_route_match(request.request_method, request.path)
|
19
|
+
|
20
|
+
puts "%s %s %s => %s" % [
|
21
|
+
self.class,
|
22
|
+
request.request_method,
|
23
|
+
request.path,
|
24
|
+
match.destination.inspect
|
25
|
+
]
|
26
|
+
|
27
|
+
response = case match.destination
|
28
|
+
when String
|
29
|
+
name, method = match.destination.split("#")
|
30
|
+
klass = IrcMachine::Controller.const_get(name)
|
31
|
+
klass.dispatch(@session, request, method, match.match)
|
32
|
+
else
|
33
|
+
Rack::Response.new "Unhandled destination: #{match.destination.class}", 500
|
34
|
+
end
|
35
|
+
|
36
|
+
response.finish
|
37
|
+
end
|
38
|
+
|
39
|
+
def get(route, destination); connect :get, route, destination; end
|
40
|
+
def put(route, destination); connect :put, route, destination; end
|
41
|
+
def delete(route, destination); connect :delete, route, destination; end
|
42
|
+
def post(route, destination); connect :post, route, destination; end
|
43
|
+
|
44
|
+
def connect(method, pattern, destination)
|
45
|
+
@routes[method] << [ pattern, destination ]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def lookup_route_match(request_method, path)
|
51
|
+
# this is pretty bad..
|
52
|
+
request_method = request_method.downcase.to_sym
|
53
|
+
route_match = OpenStruct.new
|
54
|
+
_, route_match.destination = @routes[request_method].detect do |(pattern,destination)|
|
55
|
+
if pattern.is_a? Regexp
|
56
|
+
route_match.match = pattern.match(path)
|
57
|
+
else
|
58
|
+
route_match.match = nil
|
59
|
+
pattern == path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
route_match
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module IrcMachine
|
4
|
+
|
5
|
+
class HttpServer < EM::Connection
|
6
|
+
|
7
|
+
include EM::HttpServer
|
8
|
+
|
9
|
+
attr_accessor :router
|
10
|
+
|
11
|
+
def process_http_request
|
12
|
+
send_response(*router.route(rack_env))
|
13
|
+
rescue => e
|
14
|
+
puts "!! #{self.class} rescued #{e.inspect}"
|
15
|
+
puts " " + e.backtrace.join("\n ")
|
16
|
+
send_response 500, {}, [ e.inspect, "\n" ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_response(status, headers, body)
|
20
|
+
EM::DelegatedHttpResponse.new(self).tap do |r|
|
21
|
+
r.status = status
|
22
|
+
r.headers = headers
|
23
|
+
# Rack body is only guaranteed to respond to #each
|
24
|
+
r.content = "".tap { |c| body.each { |b| c << b } }
|
25
|
+
r.send_response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def rack_env
|
30
|
+
# TODO: map headers to HTTP_... keys.
|
31
|
+
{
|
32
|
+
"rack.version" => [1, 1],
|
33
|
+
"rack.url_scheme" => @http_protocol,
|
34
|
+
"rack.input" => StringIO.new((@http_post_content || "")),
|
35
|
+
"rack.errors" => open("/dev/null", "w"), # muahaha
|
36
|
+
"rack.multithread" => false,
|
37
|
+
"rack.multiprocess" => false,
|
38
|
+
"rack.run_once" => false,
|
39
|
+
|
40
|
+
"REQUEST_METHOD" => @http_request_method,
|
41
|
+
"SCRIPT_NAME" => "", # see PATH_INFO
|
42
|
+
"PATH_INFO" => @http_path_info,
|
43
|
+
"QUERY_STRING" => @http_query_string,
|
44
|
+
"SERVER_NAME" => nil, # illegally nil
|
45
|
+
"SERVER_PORT" => nil, # illegally nil
|
46
|
+
"REMOTE_ADDR" => remote_ip, # not in spec
|
47
|
+
|
48
|
+
"HTTP_COOKIE" => @http_cookie,
|
49
|
+
"HTTP_IF_NONE_MATCH" => @http_if_none_match,
|
50
|
+
"HTTP_CONTENT_TYPE" => @http_content_type,
|
51
|
+
"HTTP_X_AUTH" => headers["X-Auth"],
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_ip
|
56
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
57
|
+
ip
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers
|
61
|
+
@headers ||= parse_headers(@http_headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
# ghetto
|
65
|
+
def parse_headers(header)
|
66
|
+
header.split("\x00").inject({}) do |headers, line|
|
67
|
+
headers[$1] = $2 if line =~ /^([^:]+):\s*(.*)$/
|
68
|
+
headers
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
class IrcConnection < EM::Connection
|
3
|
+
include EM::Protocols::LineText2
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
@ssl = opts[:ssl]
|
7
|
+
@session = opts[:session]
|
8
|
+
end
|
9
|
+
|
10
|
+
alias_method :orig_post_init, :post_init
|
11
|
+
|
12
|
+
def post_init
|
13
|
+
if @ssl
|
14
|
+
@session.log "Initializing SSL connection"
|
15
|
+
@ssl_buffer = ""
|
16
|
+
start_tls
|
17
|
+
else
|
18
|
+
orig_post_init
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ssl_handshake_completed
|
23
|
+
@session.log "SSL handshake complete"
|
24
|
+
orig_post_init
|
25
|
+
buffer = @ssl_buffer
|
26
|
+
@ssl_buffer = nil
|
27
|
+
send_data buffer
|
28
|
+
end
|
29
|
+
|
30
|
+
def send_data(data)
|
31
|
+
if @ssl_buffer
|
32
|
+
@ssl_buffer << data
|
33
|
+
else
|
34
|
+
super(data)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def receive_line(line)
|
39
|
+
@session.receive_line(line)
|
40
|
+
rescue => e
|
41
|
+
@session.log "!! #{self.class} rescued #{e.inspect}"
|
42
|
+
@session.log(" " + e.backtrace.join("\n "))
|
43
|
+
end
|
44
|
+
|
45
|
+
def unbind
|
46
|
+
@session.log "Disconnected"
|
47
|
+
@session.disconnected
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "json"
|
2
|
+
require "cgi"
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module IrcMachine
|
6
|
+
module Plugin
|
7
|
+
|
8
|
+
class GithubNotifier < Plugin::Base
|
9
|
+
end
|
10
|
+
|
11
|
+
class GithubNotification
|
12
|
+
|
13
|
+
attr_reader :data
|
14
|
+
|
15
|
+
def initialize(body)
|
16
|
+
json = CGI.parse(body)["payload"][0]
|
17
|
+
@data = OpenStruct.new(JSON.parse(json))
|
18
|
+
end
|
19
|
+
|
20
|
+
def repo_name
|
21
|
+
data.repository["name"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def commit_count
|
25
|
+
data.commits.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def branch
|
29
|
+
data.ref.split("/").last
|
30
|
+
end
|
31
|
+
|
32
|
+
def authors
|
33
|
+
data.commits.map{ |c| OpenStruct.new c["author"] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def author_usernames
|
37
|
+
authors.map{ |a| a.username }.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def compare_url
|
41
|
+
data.compare
|
42
|
+
end
|
43
|
+
|
44
|
+
def message
|
45
|
+
"%d commit%s by %s pushed to %s/%s: %s" % [
|
46
|
+
commit_count,
|
47
|
+
commit_count == 1 ? "" : "s",
|
48
|
+
author_usernames.join(", "),
|
49
|
+
repo_name,
|
50
|
+
branch,
|
51
|
+
compare_url
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class IrcMachine::Plugin::Reloader < IrcMachine::Plugin::Base
|
2
|
+
|
3
|
+
def receive_line(line)
|
4
|
+
if line =~ /^:\S+ PRIVMSG #{session.state.nick} :reload$/
|
5
|
+
self.class.load_all
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.load_all
|
10
|
+
files = %w{
|
11
|
+
core
|
12
|
+
core_routes
|
13
|
+
|
14
|
+
udp_server
|
15
|
+
|
16
|
+
http_controller
|
17
|
+
http_router
|
18
|
+
http_server
|
19
|
+
|
20
|
+
controller/channels_controller
|
21
|
+
controller/github_notifications_controller
|
22
|
+
|
23
|
+
plugin/github_notifier
|
24
|
+
plugin/base
|
25
|
+
plugin/hello
|
26
|
+
plugin/reloader
|
27
|
+
}.each do |name|
|
28
|
+
puts "loading: #{name}"
|
29
|
+
load "irc_machine/#{name}.rb"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
class Session
|
3
|
+
include Commands
|
4
|
+
|
5
|
+
attr_reader :options
|
6
|
+
attr_reader :state
|
7
|
+
attr_accessor :irc_connection
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
IrcMachine::Plugin::Reloader.load_all
|
11
|
+
|
12
|
+
@options = OpenStruct.new(options)
|
13
|
+
@state = State.new
|
14
|
+
@router = HttpRouter.new(self)
|
15
|
+
@plugins = [
|
16
|
+
Core.new(self),
|
17
|
+
Plugin::Hello.new(self),
|
18
|
+
Plugin::Reloader.new(self),
|
19
|
+
Plugin::GithubNotifier.new(self)
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
EM.run do
|
25
|
+
|
26
|
+
signal_traps
|
27
|
+
|
28
|
+
log "Connecting to #{options.server}:#{options.port}"
|
29
|
+
EM.connect(
|
30
|
+
options.server,
|
31
|
+
options.port,
|
32
|
+
IrcConnection,
|
33
|
+
{:ssl => @options.ssl, :session => self}
|
34
|
+
) do |c|
|
35
|
+
self.irc_connection = c
|
36
|
+
end
|
37
|
+
|
38
|
+
log "Starting HTTP API on port #{options.http_port}"
|
39
|
+
EM.start_server "0.0.0.0", options.http_port, HttpServer do |c|
|
40
|
+
c.router = @router
|
41
|
+
end
|
42
|
+
|
43
|
+
log "Starting UDP API on port #{options.udp_port}"
|
44
|
+
EM.open_datagram_socket "0.0.0.0", options.udp_port, UdpServer do |c|
|
45
|
+
c.session = self
|
46
|
+
end
|
47
|
+
|
48
|
+
dispatch :connected
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def disconnected
|
53
|
+
if @shutdown
|
54
|
+
log "Stopping EventMachine"
|
55
|
+
EM.stop
|
56
|
+
else
|
57
|
+
log "Waiting to reconnect"
|
58
|
+
EM.add_timer(2) do
|
59
|
+
log "Reconnecting to #{options.server}:#{options.port}"
|
60
|
+
irc_connection.reconnect options.server, options.port
|
61
|
+
@state.reset
|
62
|
+
dispatch :connected
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def receive_line(line)
|
68
|
+
dispatch :receive_line, line
|
69
|
+
end
|
70
|
+
|
71
|
+
def log message
|
72
|
+
puts "! " << message if options.verbose
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def dispatch(method, *params)
|
78
|
+
@plugins.each { |p| p.send(method, *params) if p.respond_to? method }
|
79
|
+
end
|
80
|
+
|
81
|
+
def signal_traps
|
82
|
+
Signal.trap("INT") { shutdown }
|
83
|
+
end
|
84
|
+
|
85
|
+
def shutdown
|
86
|
+
@shutdown = true
|
87
|
+
Signal.trap("INT") do
|
88
|
+
Signal.trap("INT", "DEFAULT")
|
89
|
+
puts "\nStopping EventMachine, interrupt again to force exit"
|
90
|
+
EM.stop
|
91
|
+
end
|
92
|
+
puts "\nQuitting IRC, interrupt again to stop EventMachine"
|
93
|
+
dispatch :terminate
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
class State
|
3
|
+
|
4
|
+
attr_accessor :nick
|
5
|
+
attr_accessor :channels
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
reset
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset
|
12
|
+
@nick = nil
|
13
|
+
@channels = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def channel?(channel)
|
17
|
+
channels.include? channel
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IrcMachine
|
2
|
+
|
3
|
+
class UdpServer < EM::Connection
|
4
|
+
|
5
|
+
attr_writer :session
|
6
|
+
|
7
|
+
def receive_data(data)
|
8
|
+
if data =~ /^PRIVMSG (#+\S+) :(.{1,1024})/
|
9
|
+
@session.msg $1, $2
|
10
|
+
else
|
11
|
+
puts "Unrecognized UDP: " << data.inspect
|
12
|
+
end
|
13
|
+
rescue => e
|
14
|
+
puts "!! #{self.class} rescued #{e.inspect}"
|
15
|
+
puts " " + e.backtrace.join("\n ")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: irc_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul Annesley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70147236217340 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70147236217340
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: eventmachine_httpserver
|
27
|
+
requirement: &70147236216920 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70147236216920
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rack
|
38
|
+
requirement: &70147236216500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70147236216500
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: daemons
|
49
|
+
requirement: &70147236216080 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70147236216080
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: bundler
|
60
|
+
requirement: &70147236215660 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70147236215660
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: &70147236215240 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70147236215240
|
80
|
+
description: An IRC bot using EventMachine, and perhaps ØMQ one day.
|
81
|
+
email:
|
82
|
+
- paul@annesley.cc
|
83
|
+
executables:
|
84
|
+
- irc_machine
|
85
|
+
- irc_machined
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- .gitignore
|
90
|
+
- .rvmrc
|
91
|
+
- Gemfile
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- bin/irc_machine
|
95
|
+
- bin/irc_machined
|
96
|
+
- example.json
|
97
|
+
- irc_machine.gemspec
|
98
|
+
- lib/irc_machine.rb
|
99
|
+
- lib/irc_machine/cli_shared.rb
|
100
|
+
- lib/irc_machine/commands.rb
|
101
|
+
- lib/irc_machine/controller/channels_controller.rb
|
102
|
+
- lib/irc_machine/controller/github_notifications_controller.rb
|
103
|
+
- lib/irc_machine/core.rb
|
104
|
+
- lib/irc_machine/core_routes.rb
|
105
|
+
- lib/irc_machine/http_controller.rb
|
106
|
+
- lib/irc_machine/http_router.rb
|
107
|
+
- lib/irc_machine/http_server.rb
|
108
|
+
- lib/irc_machine/irc_connection.rb
|
109
|
+
- lib/irc_machine/monkey_patches.rb
|
110
|
+
- lib/irc_machine/plugin.rb
|
111
|
+
- lib/irc_machine/plugin/base.rb
|
112
|
+
- lib/irc_machine/plugin/github_notifier.rb
|
113
|
+
- lib/irc_machine/plugin/hello.rb
|
114
|
+
- lib/irc_machine/plugin/reloader.rb
|
115
|
+
- lib/irc_machine/session.rb
|
116
|
+
- lib/irc_machine/state.rb
|
117
|
+
- lib/irc_machine/udp_server.rb
|
118
|
+
- lib/irc_machine/version.rb
|
119
|
+
homepage: https://github.com/pda/irc_machine
|
120
|
+
licenses: []
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.8.11
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: irc machine
|
143
|
+
test_files: []
|
144
|
+
has_rdoc:
|