├── .travis.yml ├── .gitignore ├── Gemfile ├── Rakefile ├── carrier-pigeon.gemspec ├── MIT-LICENSE.txt ├── README.org ├── lib └── carrier-pigeon.rb └── test └── send_tests.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in carrier-pigeon.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | require "rake/testtask" 3 | 4 | Bundler::GemHelper.install_tasks 5 | 6 | Rake::TestTask.new do |test| 7 | test.pattern = "test/*_tests.rb" 8 | end 9 | 10 | task :default => "test" 11 | -------------------------------------------------------------------------------- /carrier-pigeon.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "carrier-pigeon" 3 | s.version = "0.7.0" 4 | s.platform = Gem::Platform::RUBY 5 | s.authors = ["Sean Porter"] 6 | s.email = ["portertech@gmail.com"] 7 | s.homepage = "https://github.com/portertech/carrier-pigeon" 8 | s.summary = %q{The simplest library to say something on IRC} 9 | s.description = %q{The simplest library to say something on IRC} 10 | s.has_rdoc = false 11 | s.license = "MIT" 12 | 13 | s.rubyforge_project = "carrier-pigeon" 14 | 15 | s.add_dependency("addressable") 16 | 17 | s.add_development_dependency("rake") 18 | 19 | s.files = `git ls-files`.split("\n") 20 | s.require_paths = ["lib"] 21 | end 22 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Sean Porter 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | [[https://secure.travis-ci.org/portertech/carrier-pigeon.png]] 2 | 3 | * Install 4 | 5 | : gem install carrier-pigeon 6 | 7 | * Usage 8 | 9 | : require "carrier-pigeon" 10 | 11 | Send a private message 12 | 13 | : CarrierPigeon.send( 14 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 15 | : :message => "cooooo, coo coo" 16 | : ) 17 | 18 | Send a notice 19 | 20 | : CarrierPigeon.send( 21 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 22 | : :message => "cooooo, coo coo", 23 | : :notice => true 24 | : ) 25 | 26 | Use SSL 27 | 28 | : CarrierPigeon.send( 29 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 30 | : :message => "coo, secret plan", 31 | : :ssl => true 32 | : ) 33 | 34 | Join a channel (required for most Freenode channels) 35 | 36 | : CarrierPigeon.send( 37 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 38 | : :message => "cooooo, part of the flock", 39 | : :join => true 40 | : ) 41 | 42 | Join a channel that requires a password 43 | 44 | : CarrierPigeon.send( 45 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 46 | : :message => "coo, the password is ..." 47 | : :channel_password => "secret", 48 | : :join => true 49 | : ) 50 | 51 | Identify with NickServ 52 | 53 | : CarrierPigeon.send( 54 | : :uri => "irc://nick:password@irc.domain.com:6667/#channel", 55 | : :message => "nickserv, coo coo", 56 | : :nickserv_password => "secret" 57 | : ) 58 | 59 | 60 | -------------------------------------------------------------------------------- /lib/carrier-pigeon.rb: -------------------------------------------------------------------------------- 1 | require "addressable/uri" 2 | require "socket" 3 | require "openssl" 4 | 5 | class CarrierPigeon 6 | 7 | def initialize(options={}) 8 | [:host, :port, :nick, :channel].each do |option| 9 | raise "You must provide an IRC #{option}" unless options.has_key?(option) 10 | end 11 | tcp_socket = TCPSocket.new(options[:host], options[:port]) 12 | if options[:ssl] 13 | ssl_context = OpenSSL::SSL::SSLContext.new 14 | ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE 15 | @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context) 16 | @socket.sync = true 17 | @socket.sync_close = true 18 | @socket.connect 19 | else 20 | @socket = tcp_socket 21 | end 22 | sendln "PASS #{options[:password]}" if options[:password] 23 | sendln "NICK #{options[:nick]}" 24 | sendln "USER #{options[:nick]} 0 * :#{options[:nick]}" 25 | while line = @socket.gets 26 | case line 27 | when /00[1-4] #{Regexp.escape(options[:nick])}/ 28 | break 29 | when /^PING :(.+)$/i 30 | sendln "PONG :#{$1}" 31 | end 32 | end 33 | sendln options[:nickserv_command] if options[:nickserv_command] 34 | if options[:join] 35 | join = "JOIN #{options[:channel]}" 36 | join += " #{options[:channel_password]}" if options[:channel_password] 37 | sendln join 38 | end 39 | end 40 | 41 | def message(channel, message, notice = false) 42 | command = notice ? "NOTICE" : "PRIVMSG" 43 | sendln "#{command} #{channel} :#{message}" 44 | end 45 | 46 | def die 47 | sendln "QUIT :quit" 48 | @socket.gets until @socket.eof? 49 | @socket.close 50 | end 51 | 52 | def self.send(options={}) 53 | raise "You must supply a valid IRC URI" unless options[:uri] 54 | raise "You must supply a message" unless options[:message] 55 | uri = Addressable::URI.parse(options[:uri]) 56 | options[:host] = uri.host 57 | options[:port] = uri.port || 6667 58 | options[:nick] = uri.user 59 | options[:password] = uri.password 60 | options[:channel] = "#" + uri.fragment 61 | if options[:nickserv_password] 62 | options[:nickserv_command] ||= 63 | "PRIVMSG NICKSERV :IDENTIFY #{options[:nickserv_password]}" 64 | end 65 | pigeon = new(options) 66 | pigeon.message(options[:channel], options[:message], options[:notice]) 67 | pigeon.die 68 | end 69 | 70 | private 71 | 72 | def sendln(cmd) 73 | @socket.puts(cmd) 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /test/send_tests.rb: -------------------------------------------------------------------------------- 1 | require "minitest/spec" 2 | require "minitest/autorun" 3 | require "socket" 4 | 5 | require File.join(File.dirname(__FILE__), "..", "lib", "carrier-pigeon") 6 | 7 | PRIVATE_MESSAGE = <<"EXPECTED" 8 | NICK foo 9 | USER foo 0 * :foo 10 | PRIVMSG #test :test 11 | QUIT :quit 12 | EXPECTED 13 | 14 | NOTICE = <<"EXPECTED" 15 | NICK foo 16 | USER foo 0 * :foo 17 | NOTICE #test :test 18 | QUIT :quit 19 | EXPECTED 20 | 21 | JOIN = <<"EXPECTED" 22 | NICK foo 23 | USER foo 0 * :foo 24 | JOIN #test 25 | PRIVMSG #test :test 26 | QUIT :quit 27 | EXPECTED 28 | 29 | PONG = <<"EXPECTED" 30 | NICK foo 31 | USER foo 0 * :foo 32 | PONG :dummy 33 | PRIVMSG #test :test 34 | QUIT :quit 35 | EXPECTED 36 | 37 | JOIN_PASSWORD = <<"EXPECTED" 38 | NICK foo 39 | USER foo 0 * :foo 40 | JOIN #test bar 41 | PRIVMSG #test :test 42 | QUIT :quit 43 | EXPECTED 44 | 45 | NICKSERV_PASSWORD = <<"EXPECTED" 46 | NICK foo 47 | USER foo 0 * :foo 48 | PRIVMSG NICKSERV :IDENTIFY bar 49 | PRIVMSG #test :test 50 | QUIT :quit 51 | EXPECTED 52 | 53 | describe CarrierPigeon do 54 | before do 55 | @server_received = "" 56 | @tcp_server = TCPServer.new(16667) 57 | Thread.new do 58 | socket = @tcp_server.accept 59 | server_messages.each { |msg| socket.puts msg } 60 | while line = socket.gets 61 | @server_received << line 62 | socket.close if line =~ /:quit/ 63 | end 64 | end 65 | end 66 | 67 | after do 68 | @tcp_server.close 69 | sleep 1 70 | end 71 | 72 | describe "with server reply" do 73 | let :server_messages do 74 | [ 75 | ":dummy 001 foo :Welcome to the Internet Relay Network", 76 | ":dummy 002 foo :Your host is dummy", 77 | ":dummy 003 foo :This server was created now", 78 | ":dummy 004 foo dummy" 79 | ] 80 | end 81 | 82 | it "can send a private message to an irc channel" do 83 | CarrierPigeon.send( 84 | :uri => "irc://foo@localhost:16667/#test", 85 | :message => "test" 86 | ) 87 | @server_received.must_equal(PRIVATE_MESSAGE) 88 | end 89 | 90 | it "can send a notice to an irc channel" do 91 | CarrierPigeon.send( 92 | :uri => "irc://foo@localhost:16667/#test", 93 | :message => "test", 94 | :notice => true 95 | ) 96 | @server_received.must_equal(NOTICE) 97 | end 98 | 99 | it "can join an irc channel and send a private message" do 100 | CarrierPigeon.send( 101 | :uri => "irc://foo@localhost:16667/#test", 102 | :message => "test", 103 | :join => true 104 | ) 105 | @server_received.must_equal(JOIN) 106 | end 107 | 108 | it "can join an irc channel with a password and send a private message" do 109 | CarrierPigeon.send( 110 | :uri => "irc://foo@localhost:16667/#test", 111 | :message => "test", 112 | :channel_password => "bar", 113 | :join => true 114 | ) 115 | @server_received.must_equal(JOIN_PASSWORD) 116 | end 117 | 118 | it "can identify with nickserv and send a private message to an irc channel" do 119 | CarrierPigeon.send( 120 | :uri => "irc://foo@localhost:16667/#test", 121 | :message => "test", 122 | :nickserv_password => "bar" 123 | ) 124 | @server_received.must_equal(NICKSERV_PASSWORD) 125 | end 126 | 127 | it "must be provided an irc uri" do 128 | lambda { 129 | CarrierPigeon.send(:message => "test") 130 | }.must_raise RuntimeError 131 | end 132 | 133 | it "must be provided an irc message" do 134 | lambda { 135 | CarrierPigeon.send(:uri => "irc://foo@localhost:16667/#test") 136 | }.must_raise RuntimeError 137 | end 138 | end 139 | 140 | describe "with server PING before initial reply" do 141 | let :server_messages do 142 | [ 143 | "PING :dummy", 144 | ":dummy 001 foo :Welcome to the Internet Relay Network", 145 | ":dummy 002 foo :Your host is dummy", 146 | ":dummy 003 foo :This server was created now", 147 | ":dummy 004 foo dummy" 148 | ] 149 | end 150 | 151 | it "must reply with PONG" do 152 | CarrierPigeon.send( 153 | :uri => "irc://foo@localhost:16667/#test", 154 | :message => "test" 155 | ) 156 | @server_received.must_equal(PONG) 157 | end 158 | end 159 | end 160 | --------------------------------------------------------------------------------