irc_machine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|