Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit d3eef2f07ec48454d4e1ef7d6668821a7cbd2b8d 0 parents
@xoebus authored
1  .rspec
@@ -0,0 +1 @@
+--color
26 LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Chris Brown
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+* The names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 README.md
@@ -0,0 +1,41 @@
+# Prosophobia
+
+> All software that doesn't look like it was written in the 1960s is no
+> good to me. **I fear change.**
+
+---
+
+## Usage
+
+Set the environment variables:
+
+* `PROSO_SUBDOMAIN` - This is your Campfire subdomain.
+* `PROSO_TOKEN` - This is your authentication token for Campfire.
+
+After these are set then you're able to start up the IRC server:
+
+ $ bin/proso
+ Connecting to Campfire...
+ Starting up IRC server on port 6667...
+
+After you've done this you can connect your IRC client to `localhost`
+and you'll be able to join your Campfire rooms and chat in them just
+as if you were talking in your IRC channels.
+
+### Channel Names
+
+Your channel names are similar to the room names in Campfire except any
+spaces are replaced by dashes. For example, *The Awesome Room* would
+become *The-Awesome-Room* and could be joined by typing the command
+`/join #The-Awesome-Room`.
+
+## Contributing
+
+Send me a pull request with some beautifully formatted commit messages,
+excellent fast running tests and some awesome documentation and I'm sure
+we can some to some sort of arrangement.
+
+## Status
+
+The project works at a basic level but it is still in very early stages
+and so I wouldn't depend on it.
21 bin/proso
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+require 'eventmachine'
+require 'tinder'
+
+$LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', 'lib'))
+require 'prosophobia'
+
+SUBDOMAIN = ENV['PROSO_SUBDOMAIN']
+TOKEN = ENV['PROSO_TOKEN']
+HOST = ENV['PROSO_HOST'] || '127.0.0.1'
+PORT = ENV['PROSO_PORT'] || 6667
+
+puts "Connecting to Campfire..."
+campfire = Tinder::Campfire.new SUBDOMAIN, :token => TOKEN
+session = Prosophobia::Session.new(campfire)
+
+puts "Starting up IRC server on port #{PORT}..."
+EventMachine.run do
+ EventMachine.start_server HOST, PORT, Server, session
+end
10 lib/prosophobia.rb
@@ -0,0 +1,10 @@
+require 'prosophobia/codes'
+require 'prosophobia/server'
+require 'prosophobia/session'
+
+require 'prosophobia/message'
+require 'prosophobia/message_parser'
+
+module Prosophobia
+ Version = VERSION = '0.0.1'
+end
139 lib/prosophobia/codes.rb
@@ -0,0 +1,139 @@
+# Gratefully stolen from: https://github.com/andrzejkrzywda/ruby-ircd
+
+module Prosophobia
+ RPL_WELCOME = 001
+ RPL_YOURHOST = 002
+ RPL_CREATED = 003
+ RPL_MYINFO = 004
+ RPL_BOUNCE = 005
+ RPL_TRACELINK = 200
+ RPL_TRACECONNECTING = 201
+ RPL_TRACEHANDSHAKE = 202
+ RPL_TRACEUNKNOWN = 203
+ RPL_TRACEOPERATOR = 204
+ RPL_TRACEUSER = 205
+ RPL_TRACESERVER = 206
+ RPL_TRACESERVICE = 207
+ RPL_TRACENEWTYPE = 208
+ RPL_TRACECLASS = 209
+ RPL_TRACERECONNECT = 210
+ RPL_TRACELOG = 261
+ RPL_TRACEEND = 262
+ RPL_STATSLINKINFO = 211
+ RPL_STATSCOMMANDS = 212
+ RPL_ENDOFSTATS = 219
+ RPL_STATSUPTIME = 242
+ RPL_STATSOLINE = 243
+ RPL_UMODEIS = 221
+ RPL_SERVLIST = 234
+ RPL_SERVLISTEND = 235
+ RPL_LUSERCLIENT = 251
+ RPL_LUSEROP = 252
+ RPL_LUSERUNKNOWN = 253
+ RPL_LUSERCHANNELS = 254
+ RPL_LUSERME = 255
+ RPL_ADMINME = 256
+ RPL_ADMINEMAIL = 259
+ RPL_TRYAGAIN = 263
+ RPL_USERHOST = 302
+ RPL_ISON = 303
+ RPL_AWAY = 301
+ RPL_UNAWAY = 305
+ RPL_NOWAWAY = 306
+ RPL_WHOISUSER = 311
+ RPL_WHOISSERVER = 312
+ RPL_WHOISOPERATOR = 313
+ RPL_WHOISIDLE = 317
+ RPL_ENDOFWHOIS = 318
+ RPL_WHOISCHANNELS = 319
+ RPL_WHOWASUSER = 314
+ RPL_ENDOFWHOWAS = 369
+ RPL_LISTSTART = 321
+ RPL_LIST = 322
+ RPL_LISTEND = 323
+ RPL_UNIQOPIS = 325
+ RPL_CHANNELMODEIS = 324
+ RPL_NOTOPIC = 331
+ RPL_TOPIC = 332
+ RPL_INVITING = 341
+ RPL_SUMMONING = 342
+ RPL_INVITELIST = 346
+ RPL_ENDOFINVITELIST = 347
+ RPL_EXCEPTLIST = 348
+ RPL_ENDOFEXCEPTLIST = 349
+ RPL_VERSION = 351
+ RPL_WHOREPLY = 352
+ RPL_ENDOFWHO = 315
+ RPL_NAMREPLY = 353
+ RPL_LINKS = 364
+ RPL_ENDOFLINKS = 365
+ RPL_ENDOFNAMES = 366
+ RPL_BANLIST = 367
+ RPL_ENDOFBANLIST = 368
+ RPL_INFO = 371
+ RPL_ENDOFINFO = 374
+ RPL_MOTDSTART = 375
+ RPL_MOTD = 372
+ RPL_ENDOFMOTD = 376
+ RPL_YOUREOPER = 381
+ RPL_REHASHING = 382
+ RPL_YOURESERVICE = 383
+ RPL_TIME = 391
+ RPL_USERSSTART = 392
+ RPL_USERS = 393
+ RPL_ENDOFUSERS = 394
+ RPL_NOUSERS = 395
+ ERR_NOSUCHNICK = 401
+ ERR_NOSUCHSERVER = 402
+ ERR_NOSUCHCHANNEL = 403
+ ERR_CANNOTSENDTOCHAN = 404
+ ERR_TOOMANYCHANNELS = 405
+ ERR_WASNOSUCHNICK = 406
+ ERR_TOOMANYTARGETS = 407
+ ERR_NOSUCHSERVICE = 408
+ ERR_NOORIGIN = 409
+ ERR_NORECIPIENT = 411
+ ERR_NOTEXTTOSEND = 412
+ ERR_NOTOPLEVEL = 413
+ ERR_WILDTOPLEVEL = 414
+ ERR_BADMASK = 415
+ ERR_UNKNOWNCOMMAND = 421
+ ERR_NOMOTD = 422
+ ERR_NOADMININFO = 423
+ ERR_FILEERROR = 424
+ ERR_NONICKNAMEGIVEN = 431
+ ERR_ERRONEUSNICKNAME = 432
+ ERR_NICKNAMEINUSE = 433
+ ERR_NICKCOLLISION = 436
+ ERR_UNAVAILRESOURCE = 437
+ ERR_USERNOTINCHANNEL = 441
+ ERR_NOTONCHANNEL = 442
+ ERR_USERONCHANNEL = 443
+ ERR_NOLOGIN = 444
+ ERR_SUMMONDISABLED = 445
+ ERR_USERSDISABLED = 446
+ ERR_NOTREGISTERED = 451
+ ERR_NEEDMOREPARAMS = 461
+ ERR_ALREADYREGISTRED = 462
+ ERR_NOPERMFORHOST = 463
+ ERR_PASSWDMISMATCH = 464
+ ERR_YOUREBANNEDCREEP = 465
+ ERR_YOUWILLBEBANNED = 466
+ ERR_KEYSET = 467
+ ERR_CHANNELISFULL = 471
+ ERR_UNKNOWNMODE = 472
+ ERR_INVITEONLYCHAN = 473
+ ERR_BANNEDFROMCHAN = 474
+ ERR_BADCHANNELKEY = 475
+ ERR_BADCHANMASK = 476
+ ERR_NOCHANMODES = 477
+ ERR_BANLISTFULL = 478
+ ERR_NOPRIVILEGES = 481
+ ERR_CHANOPRIVSNEEDED = 482
+ ERR_CANTKILLSERVER = 483
+ ERR_RESTRICTED = 484
+ ERR_UNIQOPPRIVSNEEDED = 485
+ ERR_NOOPERHOST = 491
+ ERR_UMODEUNKNOWNFLAG = 501
+ ERR_USERSDONTMATCH = 502
+end
9 lib/prosophobia/message.rb
@@ -0,0 +1,9 @@
+module Prosophobia
+ class Message
+ attr_accessor :command, :args
+
+ def initialize(command, args)
+ @command, @args = command, args
+ end
+ end
+end
12 lib/prosophobia/message_parser.rb
@@ -0,0 +1,12 @@
+module Prosophobia
+ class MessageParser
+ MESSAGE_RE = /^([A-Z]+) ([^:]+):?(.+)?$/
+ def parse(raw_message)
+ matches = MESSAGE_RE.match(raw_message)
+ command = matches[1]
+ args = [matches[2].split, matches[3]].flatten.compact
+
+ Message.new(command, args)
+ end
+ end
+end
17 lib/prosophobia/server.rb
@@ -0,0 +1,17 @@
+class Server < EventMachine::Connection
+ attr_accessor :session
+
+ def initialize(session)
+ super
+
+ @session = session
+ @session.server = self
+ end
+
+ def receive_data(data)
+ data.split("\r\n").each do |line|
+ puts "Received: #{line}"
+ session.handle(line)
+ end
+ end
+end
104 lib/prosophobia/session.rb
@@ -0,0 +1,104 @@
+module Prosophobia
+ class Session
+
+ attr_accessor :adapter
+ attr_accessor :server
+
+ attr_reader :nickname
+ attr_reader :username
+ attr_reader :realname
+
+ def initialize(adapter, parser = MessageParser.new)
+ @adapter = adapter
+ @parser = parser
+
+ @me = @adapter.me.id
+ end
+
+ def host
+ 'localhost'
+ end
+
+ def greet
+ [
+ ":#{host} #{'%03d' % RPL_WELCOME} #{nickname} :Welcome!",
+ ":#{host} #{'%03d' % RPL_YOURHOST} #{nickname} :Your host is #{host}",
+ ":#{host} #{'%03d' % RPL_CREATED} #{nickname} :This server was created at some point.",
+ ":#{host} #{'%03d' % RPL_MYINFO} #{nickname} :This server is very interesting."
+ ].each { |message| irc_send(message) }
+ end
+
+ HANDLERS = {
+ 'NICK' => :nick_handler,
+ 'USER' => :user_handler,
+ 'JOIN' => :join_handler,
+ 'PRIVMSG' => :privmsg_handler
+ }
+
+ def handle(message)
+ message = @parser.parse(message)
+ method = HANDLERS.fetch(message.command) { return [] }
+ send(method, message)
+ end
+
+ def nick_handler(message)
+ @nickname = message.args.first
+ end
+
+ def user_handler(message)
+ @username, _, _, @realname = message.args
+ greet
+ end
+
+ def join_handler(message)
+ channel = message.args.first
+ channels = channel.split(",")
+
+ channels.each do |channel|
+ irc_send ":#{nickname}!~#{username}@#{host} JOIN :#{channel}"
+ room = @adapter.find_room_by_name(channel2room(channel))
+
+ irc_send ":#{host} #{'%03d' % RPL_TOPIC} #{nickname} #{channel} :#{room.topic}"
+ irc_send ":#{host} #{'%03d' % RPL_NAMREPLY} = #{nickname} #{channel} :#{namelist(room)}"
+ irc_send ":#{host} #{'%03d' % RPL_ENDOFNAMES} #{nickname} #{channel} :End of /NAMES list."
+
+ room.listen do |m|
+ next unless m.user?
+ next if m.user.id == @me
+ name = name2nick(m.user.name)
+ irc_send ":#{name}!~#{name}@#{host} PRIVMSG #{channel} :#{m.body}\r\n"
+ end
+ end
+ end
+
+ def privmsg_handler(message)
+ channel, text = message.args
+ room = @adapter.find_room_by_name(channel2room(channel))
+ room.speak text
+ end
+
+ def irc_send(message)
+ puts "Sending: #{message}"
+ @server.send_data("#{message}\r\n")
+ end
+
+ def room2channel name
+ "##{name.gsub(/ /, "-")}"
+ end
+
+ def channel2room name
+ name.gsub(/-/, " ")[1..-1]
+ end
+
+ def name2nick name
+ name[0..7]
+ end
+
+ def namelist room
+ users = room.users.reject { |user| user.id == @me }
+ users.map do |user|
+ "#{"@" if user.admin?}#{name2nick(user.name)}"
+ end.join(" ")
+ end
+ end
+end
23 spec/message_parser_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Prosophobia::MessageParser, "parsing" do
+ it "parses the command from the message" do
+ parser = Prosophobia::MessageParser.new
+
+ message = parser.parse("NICK cb")
+ message.command.should == "NICK"
+
+ message = parser.parse("USER cb cb localhost :Chris Brown")
+ message.command.should == "USER"
+ end
+
+ it "parses the args from the message" do
+ parser = Prosophobia::MessageParser.new
+
+ message = parser.parse("NICK cb")
+ message.args.should == ["cb"]
+
+ message = parser.parse("USER cb cb localhost :Chris Brown")
+ message.args.should == ["cb", "cb", "localhost", "Chris Brown"]
+ end
+end
17 spec/message_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Prosophobia::Message do
+ describe 'constructor' do
+ before :each do
+ @message = Prosophobia::Message.new("COMMAND", ["some", "args", "here"])
+ end
+
+ it 'sets commmand attribute' do
+ @message.command = "COMMAND"
+ end
+
+ it 'sets args attribute' do
+ @message.args = ["some", "args", "here"]
+ end
+ end
+end
34 spec/session_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Prosophobia::Session do
+ describe 'constructor' do
+ before :each do
+ @adapter = double('adapter')
+ @session = Prosophobia::Session.new(@adapter)
+ end
+
+ it 'sets the adapter' do
+ @session.adapter.should == @adapter
+ end
+ end
+
+ describe 'handles messages' do
+ before :each do
+ @parser = double('parser')
+ @session = Prosophobia::Session.new(@adapter, @parser)
+ end
+
+ it 'returns nothing when command is unrecognised' do
+ @parser.stub(:parse) { double("message", :command => "WHATTHEFUCK") }
+ @session.handle("WHATTHEFUCK blah blah").should == []
+ end
+
+ it 'calls a method from the hash' do
+ @message = double("message", :command => "NICK")
+ @parser.stub(:parse) { @message }
+ @session.should_receive(:nick_handler).with(@message)
+
+ @session.handle("")
+ end
+ end
+end
6 spec/spec_helper.rb
@@ -0,0 +1,6 @@
+require 'rspec'
+
+require 'eventmachine'
+
+$LOAD_PATH.unshift File.expand_path(File.join(__FILE__, '..', '..', 'lib'))
+require 'prosophobia'
Please sign in to comment.
Something went wrong with that request. Please try again.