Created
March 7, 2016 04:03
-
-
Save scottwalters/9c3c693fa93d2dcb3567 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
=for comment | |
MUD<->IRC proxy | |
Presents MUD as an IRC server | |
Todo | |
---- | |
v/ authentication and login -- user should be prompted for password, or password | |
should be PRIVMSG'd to the 'game' psuedo-user. once the nick and password | |
are both specified, send login info. | |
v/ MOTD/banners | |
v/ from client: ACTION performs commands, PRIVMSG does a 'say' | |
o. meaningful error messages -- no such user, connection to server closed, | |
etc. 421 -- unknown command -- that's it. | |
o. need users in current room (like 'look') and on the game (like 'who') | |
http://www.valinor.sorcery.net/docs/rfc1459/ -- nice HTML-izied, cross referenced | |
guide to the IRC protocol. | |
=cut | |
package WeeIRC; | |
use strict; | |
use warnings; | |
no warnings 'uninitialized'; | |
use Socket; | |
use POSIX qw(:errno_h); | |
use Sys::Hostname; | |
# use Data::Alias; | |
# use Error ':try'; | |
use IO::Handle; | |
use Event; | |
use Coro::Event; | |
use Coro; | |
use Coro::Handle; | |
use Coro::Socket; | |
our $VERSION = '0.2'; | |
my $EOF = "\r\n"; | |
my $serverport = 6667; | |
################################################# | |
## DEBUGGING | |
# $SIG{__DIE__} = $SIG{INT} = sub { | |
# # when someone does kill -INT <our pid> from the command line, dump our stack and exit | |
# STDERR->print(shift, map { (caller($_))[0] ? sprintf("%s at line %d\n", (caller($_))[1,2]) : ''; } 0..30); | |
# STDERR->print(join "\n", @_); | |
# exit 1; | |
# }; | |
open my $debug, '>>', '/dev/null' or die $!; | |
# open my $debug, '>>', 'wee-irc-debug.log' or die $!; | |
# my $debug = \*STDOUT; # doesn't register as a filehandle | |
sub debug { | |
$debug->print(@_); | |
$debug->flush; | |
} | |
local $SIG{PIPE} = sub { | |
# debug("sig pipe, $!, " . join " ", @_); | |
# debug("\n"); | |
}; | |
my $verboseLvl = 5; | |
################################################# | |
## START | |
# global state | |
# my $host = 'weehours.net'; | |
my $host = 'localhost'; | |
my $weehours = inet_ntoa scalar gethostbyname $host; | |
my @players; | |
my @sessions; | |
local $SIG{TERM} = sub { | |
foreach my $session (@sessions) { | |
close $session->client; | |
close $session->server; | |
} | |
exit; | |
}; | |
# | |
# accept connections | |
# | |
async { | |
my $listen; | |
# local $SIG{__DIE__}; | |
eval { $listen = Coro::Socket->new( | |
LocalPort => $serverport, | |
Type => SOCK_STREAM, | |
Listen => SOMAXCONN, | |
) } or die $@; | |
# $listen->autoflush(1); | |
while(1) { | |
# | |
# accept and proxy connections | |
# | |
debug("accept: 1\n"); | |
my $client = $listen->accept(); | |
debug("accept: 2\n"); | |
$client or next; | |
$client->autoflush(1); | |
# trace( "SERVER: accepted a connection from " . inet_ntoa($client->peeraddr()), 7 ); | |
trace( "SERVER: accepted a connection from unknown", 7 ); | |
# | |
# chat with the game | |
# | |
# | |
# persistant shared state | |
# | |
my $session; $session = do { | |
package WeeIRC::Session; | |
use Socket; | |
use Attribute::Property; | |
sub new :New; | |
sub nick :Property; | |
sub channel :Property; | |
sub current_command :Property; | |
sub last_command :Property; | |
sub client :Property; | |
sub server :Property; | |
sub send_server { $_[0]->server or return; eval { $_[0]->server->print($_[1], "\n"); }; eval { WeeIRC::debug("SERVER OUT: $_[1]\n"); 1; }; }; | |
sub send_client { $_[0]->client or return; eval { $_[0]->client->print($_[1], "\r\n"); }; WeeIRC::debug("CLIENT OUT: $_[1]\n") or die $!; }; | |
sub connect { | |
$_[0]->server = Coro::Socket->new( | |
PeerAddr => $weehours, | |
PeerPort => 2000, | |
Type => SOCK_STREAM, | |
# Blocking => 1, | |
Autoflush => 1, | |
); | |
}; | |
__PACKAGE__; | |
}->new(client => $client); | |
push @sessions, $session; | |
# XXX testing | |
# $session->nick = 'test'; | |
# die unless $session->nick eq 'test'; | |
# $session->nick = undef; | |
# start a new execution context for the IRC client -- that context will later spawn | |
# an execution context for the MUD if all goes well | |
async { irc_accept($session); }; | |
} | |
}; | |
# | |
# Coro::Event runloop | |
# | |
# Coro::Event is insane and wants us to have at least one event... or something | |
async { | |
my $timer = Coro::Event->timer(interval => 300, ); | |
1 while $timer->next; | |
}; | |
cede for 1..10; | |
Event::loop(); | |
exit; | |
################################################# | |
## IRC CLIENT HANDLER | |
sub irc_accept { | |
# debug("debug: in irc_accept ", __LINE__, "\n"); | |
my $session = shift() or return; | |
# debug("debug: in irc_accept ", __LINE__, "\n"); | |
my $client = $session->client or die; # return; | |
# debug("debug: in irc_accept ", __LINE__, "\n"); | |
my $parse_command = sub { | |
# | |
# read a line of input and digest it | |
# | |
# debug("client: 1\n"); $debug->flush; | |
my $input; | |
POLL: | |
$input = $client->readline; | |
defined $input or goto client_done; | |
# $client->fh->eof and return 0; # eof is only valid after the first read attempt # XXX not detecting closed connection -- busy spins | |
# debug("client: 2\n"); | |
debug("CLIENT IN (@{[ $session->nick ]}): ", $input, "\n"); $debug->flush; | |
$input or goto POLL; | |
chomp $input; | |
$input or goto POLL; | |
# clients might prefix commands with ":$nick ". remove that. | |
$input =~ s{^:\S+ }{}; | |
my @arglist; | |
while($input) { | |
if(substr($input, 0, 1) eq ':') { | |
substr $input, 0, 1, undef; # remove the ':' | |
push @arglist, $input; | |
last; | |
} | |
(my $first, my $rest) = $input =~ /^(\S+) ?(.*)/ or last; | |
push @arglist, $first; | |
$input = $rest; | |
} | |
my $command = uc shift @arglist; | |
return $command, @arglist if wantarray; | |
1; | |
}; | |
# debug("debug: in irc_accept ", __LINE__, "\n"); | |
$session->send_client("NOTICE AUTH :*** WeeHours IRC Proxy $VERSION"); | |
$session->send_client("NOTICE AUTH :*** Ready to get your user and nick info from you and then connect to the game"); | |
# debug("debug: in irc_accept ", __LINE__, "\n"); | |
# | |
# this is the per-connection client-facing run-loop | |
# | |
while(1) { | |
# | |
# TCP next | |
# | |
(my $command, my @arglist) = $parse_command->() or goto client_done; | |
$arglist[-1] =~ s/[\x00-\x1f]//g if @arglist; # no nasty control codes, such as in PRIVMSG ACTIONs | |
# | |
### handle commands: | |
# | |
$session->current_command = $command; | |
# dispatch commands to handlers | |
({ | |
AUTH => \&com_AUTH, | |
NICK => \&com_NICK, | |
USER => \&com_USER, | |
PRIVMSG => \&com_PRIVMSG, | |
NOTICE => \&com_NOTICE, | |
WHO => \&com_WHO, | |
NAMES => \&com_NAMES, | |
LIST => \&com_LIST, | |
PING => \&com_PING, | |
PONG => \&com_PONG, | |
QUIT => sub { goto client_done; }, | |
JOIN => \&com_JOIN, | |
CHANNEL => \&com_JOIN, | |
MOTD => \&com_MOTD, | |
KICK => \&com_KICK, | |
MODE => \&com_MODE, | |
DEBUG => \&com_DEBUG, | |
USERHOST => \&com_USERHOST, | |
# PART => 0, # XXX should quit us off of the MUD | |
# RAW => \&com_RAW, | |
# INFO VERSION STATS -- commmands to server likely to be sent automatically by an IRC client | |
# INVITE KICK | |
}->{$command} || sub { | |
# unknown command | |
$session->send_server(lc($command) . ($arglist[-1] ? ' ' : '' ) . $arglist[-1]) or goto client_done; | |
})->( $session, @arglist ); | |
# $session->send_client(":$host 421 @{[ $session->nick ]} $command :Unknown command ``$command''"); | |
# debug("CLIENT: sent unknown command: $command\n"); | |
# XXX -- if we get a 'What ?' from the server, maybe do a 421 on $session->last_command ? | |
$session->last_command = $command; | |
} | |
client_done: | |
debug("client_done: error was: $!\n"); | |
eval { $session->send_server("quit"); $session->server->close(); }; | |
eval { $session->client->close(); }; | |
} | |
################################################# | |
# commands from the client | |
sub com_USER { | |
my $session = shift; | |
my @quad = @_; | |
# XXX - none of this data is stored, but I don't think it's needed. | |
# until this happens, the server won't really talk to the client | |
# 462 ERR_ALREADYREGISTRED | |
# $session->send_client(":$host 462 :Eh? You've already sent a USER command"); | |
} | |
sub com_NICK { | |
my $session = shift; | |
my $nick = shift; | |
# XXX should attempt to figure out if the user even exists (on the MUD side) | |
# if($session->nick) | |
if($session->channel) { | |
# now, nick isn't sent until after channel join, so they can change it before then | |
$session->send_client(":$host 436 $nick :Eh? Right or wrong, login information has already been sent to the game. /quit and relog to change name."); | |
return; | |
} | |
my $oldnick = $session->nick; | |
$session->nick = $nick; | |
unless($oldnick) { | |
$session->send_client(":$host 001 $nick :WeeHours Multi-User Dungeon IRC Gateway"); | |
$session->send_client(":$host 002 $nick :Your host is slowass.net[slowass.net/$serverport], running something Scott wrote in Perl $VERSION... hang on, connecting to the game..."); | |
$session->connect; | |
async { mud_accept($session); }; | |
com_MOTD($session); | |
$session->send_client(":$host PRIVMSG $nick :Connected! To login to the game, /join #mud, specifying $nick\'s password as the channel key."); | |
} | |
} | |
sub com_JOIN { | |
my $session = shift; | |
my $channel = shift; # XXX should check that it starts with a '#' | |
my $key = shift; | |
my $nick = $session->nick; | |
$channel eq '#mud' or do { | |
$session->send_client(":mud 403 $nick :No such channel"); # ERR_NOSUCHCHANNEL | |
return; | |
}; | |
$key or do { | |
# 475 ERR_BADCHANNELKE | |
$session->send_client(":$host 475 $nick :Specify the password for character '$nick' as the channel key"); | |
return; | |
}; | |
$session->channel = $channel; | |
$session->send_server( $nick ); # username | |
$session->send_server( $key ); # password | |
$session->send_client(":$nick JOIN :$channel"); | |
com_NAMES($session, $channel); | |
} | |
sub com_NAMES { | |
my $session = shift; | |
# everyone in the channel | |
# XXX -- currently, map.c sends a list of everyone on the game | |
$session->send_server("REQ:gui|cmd:19"); | |
} | |
sub com_WHO { | |
com_NAMES(@_); # XXX -- /who is really something else but doing this for now | |
} | |
sub com_PRIVMSG { | |
my $session = shift; | |
my $target = shift; | |
my $message = shift; | |
if(! $session->channel) { | |
# some kind of "no permission" error message XXX | |
return; | |
} | |
debug("PRIVMSG: target: $target message: $message\n"); | |
# debug(map("$_ ", map ord, split //, $message), "\n"); | |
# :woggle!~somebody@c-24-63-116-152.hsd1.comcast.net PRIVMSG #perlhelp :It's just a standard tree traversal. | |
# XXX need an IMP command for getting the names off of the 'who' list. | |
# XXX this should do a 'tell' if the target is a name on the 'who' list. | |
# XXX should recognize special targets, including 'shout' | |
if($message =~ m/^ACTION ?/) { | |
# ($message) = $message =~ m/^\001(.*)\001$/ or debug("no pesky 001's to remove?\n"); # XXX | |
debug("PRIVMSG: it's an action: message is: $message\n"); | |
# PRIVMSG #mud :ACTION smiles | |
$message =~ s/^ACTION ?//; | |
# this approach uses action verbs/feelings | |
# $message =~ s/^(\w+)s /$1 /; # /me smiles -> smile | |
# $message =~ s/^(\w+) at /$1 / unless $message =~ m/^look/; # smile at joe -> smile joe | |
# $session->send_server($message); | |
# this approach just uses emote | |
# $session->send_server("/me $message"); | |
# this approach just passes commands in-tact to the game | |
$message =~ s/^(\w+)s$/$1/; # /me smiles -> smile | |
$message =~ s/^(\w+)s /$1 /; # /me smiles -> smile | |
$message =~ s/^(\w+) at /$1 / unless $message =~ m/^look/; # smile at joe -> smile joe | |
$session->send_server($message); | |
return; | |
} elsif(lc($target) eq lc($session->nick)) { | |
# apparently one IRC client likes to message itself gibberish constantly, and the MUD | |
# drops the gibberish, and it actually wants the gibberish | |
my $nick = $session->nick; | |
my $command = $session->current_command; | |
$session->send_client("$nick!$nick\@$host $command $nick :$message"); | |
} elsif($target eq $session->channel or $target eq '#mud' or $target eq 'mud') { | |
$session->send_server("say $message"); | |
} else { | |
$target = lc $target; | |
$session->send_server("tell $target $message"); | |
} | |
return 1; | |
} | |
sub com_NOTICE { | |
com_PRIVMSG(@_); | |
} | |
sub com_MODE { | |
my $session = shift; | |
my $nick = shift; | |
my $mode = shift; | |
if($nick eq $session->nick) { | |
$mode ||= '+'; | |
$session->send_client("$host 221 $nick $mode"); | |
} elsif($nick eq $session->channel) { | |
# mode starts with + instead of : for some reason | |
$mode ||= '+sptn'; | |
$session->send_client("$host 324 @{[ $session->channel ]} $mode"); | |
} | |
# XXX -- replies are RPL_CHANNELMODEIS (324), RPL_UMODEIS (221) | |
} | |
sub com_LIST { | |
my $session = shift; | |
# lists all of the channels | |
# XXX -- just return a hard-code of #mud | |
} | |
sub com_PING { | |
my $session = shift; | |
my @args = (@_, ''); | |
$session->send_client(":$host PONG :$args[0]"); | |
} | |
sub com_PONG { | |
my $session = shift; | |
# XXX right now, we don't care we got a PONG, as we never send PINGs | |
} | |
sub com_MOTD { | |
my $session = shift; | |
my $nick = $session->nick; | |
# XXX -- should just do 'news' on the game | |
# XXX -- or just make this the title screen of the game | |
open my $motd, '<', 'motd.txt' or do { debug("no motd.txt file: $!\n"); return; }; | |
$session->send_client(":$host 375 $nick :- Message of the Day"); | |
while(my $line = <$motd>) { | |
chomp $line; | |
$session->send_client(":$host 372 $nick :- $line"); | |
} | |
$session->send_client(":$host 376 $nick :- End of Message of the Day"); | |
# 375 RPL_MOTDSTART | |
# ":- Message of the day - " | |
# 372 RPL_MOTD | |
# ":- " | |
# 376 RPL_ENDOFMOTD | |
# ":End of /MOTD command" | |
# | |
## - When responding to the MOTD message and the MOTD file | |
# is found, the file is displayed line by line, with | |
# each line no longer than 80 characters, using | |
# RPL_MOTD format replies. These should be surrounded | |
# by a RPL_MOTDSTART (before the RPL_MOTDs) and an | |
# RPL_ENDOFMOTD (after). | |
} | |
sub com_KICK { | |
my $session = shift; | |
my $chan = shift; | |
my $target = shift; | |
$target = $chan if ! $target; | |
$session->send_server("kick $target"); | |
} | |
sub com_TOPIC { | |
# 331 RPL_NOTOPIC | |
# " :No topic is set" | |
# 332 RPL_TOPIC | |
# " :" | |
# | |
# - When sending a TOPIC message to determine the | |
# channel topic, one of two replies is sent. If | |
# the topic is set, RPL_TOPIC is sent back else | |
# RPL_NOTOPIC. | |
} | |
sub com_DEBUG { | |
my $session = shift; | |
my $nick = $session->nick; | |
foreach my $user (@sessions) { | |
$session->send_client("mud!mud\@$host PRIVMSG $nick :debug: @{[ $user->nick ]}"); | |
} | |
} | |
# 302 RPL_USERHOST | |
# ":[<reply>{<space><reply>}]" | |
# | |
# - Reply format used by USERHOST to list replies to | |
# the query list. The reply string is composed as | |
# follows: | |
# | |
# <reply> ::= <nick>['*'] '=' <'+'|'-'><hostname> | |
# | |
# The '*' indicates whether the client has registered | |
# as an Operator. The '-' or '+' characters represent | |
# whether the client has set an AWAY message or not | |
# respectively. | |
sub com_USERHOST { | |
my $session = shift; | |
my @args = @_; | |
my $nick = $session->nick; | |
my $out = ''; | |
for my $arg (@args) { | |
$out .= ' ' if $out; | |
$out .= "$arg=-127.0.0.1"; | |
} | |
$session->send_client("$host 302 :$out"); | |
1; | |
} | |
################################################# | |
## MUD HANDLER | |
sub mud_accept { | |
debug("debug: in mud_accept\n"); | |
my $session = shift or return; | |
my $server = $session->server; | |
$server or do { | |
die "mud_accept: no server!"; | |
return; | |
}; | |
my $parse_command = sub { | |
my $packet; | |
my $meat; | |
parse_command_again: | |
# debug("server: 1\n"); $debug->flush; | |
$packet = $server->readline; | |
defined $packet or goto server_done; | |
# $server->read($packet, 4096); | |
# debug("server: 2\n"); | |
# debug("SERVER IN: ", $packet); $debug->flush(); | |
chomp $packet; | |
$packet =~ s/[\000-\036]//sg; | |
$packet =~ s/^> //; # get rid of the command prompt | |
if($packet =~ m/^REQ:/) { | |
# debug("server: ", __LINE__, "\n"); | |
# XXX -- this program parses IMP-over-TCP requests, but doesn't (yet) do 'make conneciton' -- FYI | |
my @fragments = split /\|/, $packet; | |
$meat = {}; | |
while(@fragments) { | |
my $frag = shift @fragments; | |
my $x = index $frag, ':'; | |
if($x == -1) { | |
debug("debug: fragment '$frag' is not a key:value pair\n"); | |
next; | |
} | |
my $key = substr $frag, 0, $x; | |
my $value = substr $frag, $x+1; | |
debug("debug: fragment '$frag' has key '$key' and value '$value'\n"); | |
if($key eq 'DATA') { | |
$value .= '|' . join '|', @fragments if @fragments; | |
@fragments = (); | |
} | |
$meat->{$key} = $value; | |
} | |
} elsif($packet =~ m/^You say in \w+: (.*)/) { | |
# filter these out -- they're just spammy | |
goto parse_command_again; | |
# } elsif($packet =~ m/^(\w+) (.*)/ and grep $1 eq $_, @players) { | |
# # experimental -- XXX | |
# # sucks too badly -- messes up who list, doesn't trigger on monsters... maybe after IMP is running | |
# $meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => '#mud', DATA => "ACTION $2" }; | |
} elsif($packet =~ m/^(\w+) says in \w+: (.*)/) { | |
# debug("server: ", __LINE__, "\n"); | |
# these alternate cases fake having text running over IMP as it should be -- stop gap | |
# cmd 7 is "push text" | |
$meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => '#mud', DATA => $2 }; | |
} elsif($packet =~ m/^(\w+) tells you in \w+: (.*)/) { | |
# debug("server: ", __LINE__, "\n"); | |
$meat = { REQ => 'gui', cmd => 7, from => "$1!$1\@$host", to => $session->nick, DATA => $2 }; | |
} else { | |
# debug("server: ", __LINE__, "\n"); | |
# everything else | |
$meat = { REQ => 'gui', cmd => 7, from => 'mud', to => '#mud', DATA => $packet }; | |
} | |
goto parse_command_again if $meat->{REQ} ne 'gui'; | |
return $meat; | |
}; | |
my $dispatch = { | |
# hot | |
7 => sub { MUD_push_text(@_) }, | |
19 => sub { MUD_names_list(@_) }, | |
# not in this version of the client interface | |
2 => sub { MUD_set_screen_size(@_) }, # client just ignores and then does a set_screen_size back | |
3 => sub { MUD_query_screen_size(@_) }, # hardcoded | |
5 => sub { MUD_scroll_screen(@_) }, | |
11 => sub { MUD_send_screenful(@_) }, # from server, it means the server is done sending the screenful | |
12 => sub { MUD_redraw_column(@_) }, | |
14 => sub { MUD_redraw_row(@_) }, | |
20 => sub { MUD_walkto(@_) }, | |
21 => sub { MUD_walkpos(@_) }, | |
22 => sub { MUD_pick(@_) }, | |
23 => sub { MUD_drop(@_) }, | |
24 => sub { MUD_join(@_) }, | |
25 => sub { MUD_part(@_) }, | |
26 => sub { MUD_create(@_) }, # game is telling the client to go into user create mode | |
}; | |
# | |
# server run loop | |
# | |
while(1) { | |
my $meat = $parse_command->(); | |
if(! exists $dispatch->{$meat->{cmd}}) { | |
debug("server: unknown command ``$meat->{cmd}'' from MUD\n"); | |
next; | |
} | |
$dispatch->{$meat->{cmd}}->($session, $meat); | |
} | |
server_done: | |
debug("MUD closed connection: $!\n"); | |
eval { local $SIG{__DIE__}; $session->server->close(); }; | |
} | |
################################################# | |
# commands from the MUD | |
sub MUD_push_text { | |
my $session = shift; | |
my $meat = shift; | |
# my $privmsg = ''; | |
# $privmsg = ":$meat->{from} " if exists $meat->{from}; | |
# $privmsg .= 'PRIVMSG '; | |
# $privmsg .= $meat->{to} . ' '; # might be user name or channel name | |
# $privmsg .= ":$meat->{DATA}"; | |
# $session->send_client($privmsg); | |
if(! $session->nick ) { | |
# messages should come from weehours.net as a sort of server message -- xchat likes this. | |
$meat->{from} = $host; | |
$meat->{to} = '*'; | |
} elsif(! $session->channel) { | |
# if we aren't in a channel, make sure the message is addressed directly to the client. | |
# even if addressed directly to the nick, it'll open a new tab in xchat if it's apparently from someone | |
$meat->{from} = $host; | |
$meat->{to} = $session->nick; | |
} | |
my $from = $meat->{from} ? ":$meat->{from} " : ''; | |
$session->send_client("${from}PRIVMSG $meat->{to} :$meat->{DATA}"); | |
} | |
sub MUD_names_list { | |
my $session = shift; | |
my $meat = shift; | |
my $nick = $session->nick; | |
my @names = split / /, $meat->{DATA}; | |
@players = @names; | |
# if(grep { uc($session->last_command) eq $_ } 'NAMES', 'JOIN', 'MODE') { | |
while(@names) { | |
my $names = join ' ', splice @names, 0, 20, (); | |
$session->send_client(":$host 353 $nick \@ #mud :$names"); | |
} | |
$session->send_client(":$host 366 $nick #mud :End of /NAMES list"); | |
# } else { | |
# # presumably WHO | |
# # XXX client doesn't like this output but I can't tell why not! ugh | |
# for my $name (@names) { | |
# # :irc.kagmir.ca 352 scrottie0 #perlhelp ~duff dragon.cbi.tamucc.edu irc.igs.ca PerlJam H :2 Jonathan Scott Duff | |
# $session->send_client(":$host 352 $nick #mud $name $host $host $name H :$name"); | |
# } | |
# $session->send_client(":$host 315 $nick * :End of /WHO list"); | |
# } | |
# /NAMES response: | |
# XXX -- sending same response to NAMES and WHO for the time being, though WHO is like 'who' on MUD | |
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :scrottie1 infinity2 scrottie sitexec NoGods S-Pulse imMute Docmarten h3h di2co czthwrk__ codesnik Riedel @Juerd m0nark Garion beeyatch kore felixhcat memelo @cfedde Delivos @xmath LabEendje webmind +mauke k0re khaladan xd R|SC xdeth Sertys CobyJones planet77 fluxion_ dufuz DeAdWoRld wolverian zeroXten | |
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :@Somni StinkfooT uri Phazorx PerlJam czth mbean drw xtor Averell @flang qw forrest_ motel6 vlad_ Micha98 +cardioid irc____ inferno__ digip1mp +knowpaced klimt @Yaakov` jeffz gib jt401 Setzer CapoeiraB nobnobnob Speed-- thegeek indigoid _farmer_ Daveman fjoms eclypse Ahnberg ravnx Skinjob Perl_warn @perl-d | |
# :irc.kagmir.ca 353 scrottie1 @ #perlhelp :CapoeiraA mortum dngor grew^M | |
# :irc.kagmir.ca 366 scrottie1 #perlhelp :End of /NAMES list.^M | |
# 353 RPL_NAMREPLY | |
# 366 RPL_ENDOFNAMES | |
} | |
# /TOPIC response: | |
# :irc.kagmir.ca 332 scrottie1 #perlhelp :This is where the help is. For Perl. Right here. In this channel. The Help. Seriously.^M | |
# :irc.kagmir.ca 333 scrottie1 #perlhelp gripe!gripe@calamitycaucus.com 1123554824^M | |
sub MUD_walkto { } | |
sub MUD_walkpos { } | |
sub MUD_set_screen_size { } | |
sub MUD_query_screen_size { } | |
sub MUD_scroll_screen { } | |
sub MUD_redraw_column { } | |
sub MUD_redraw_row { } | |
sub MUD_send_screenful { } | |
sub MUD_pick { } | |
sub MUD_drop { } | |
sub MUD_create { } | |
################################################# | |
# logging and tracing | |
sub trace { | |
my $session; $session = shift if ref $_[0]; | |
my $msg = shift; | |
my $lvl = shift() || 0; | |
my $timestamp = localtime(); | |
return unless $lvl >= $verboseLvl; | |
if($session) { | |
# XXX 421 is the "unknown command" error, I think, but I don't know what would work better | |
$session->send_client(":$host 421 @{[ $session->nick ]} COMMANDHERE :trace daignostic message``$msg''"); | |
} else { | |
debug("[$timestamp] ($lvl) $msg\n"); | |
} | |
} | |
__END__ | |
Upon connecting to an IRC server, a client is sent the MOTD (if | |
present) as well as the current user/server count (as per the LUSER | |
command). The server is also required to give an unambiguous message | |
to the client which states its name and version as well as any other | |
introductory messages which may be deemed appropriate. | |
After dealing with this, the server must then send out the new user's | |
nickname and other information as supplied by itself (USER command) | |
and as the server could discover (from DNS/authentication servers). | |
The server must send this information out with NICK first followed by | |
USER. | |
The current IRC protocol has 3 types of labels: the nickname, the | |
channel name and the server name. | |
IRC messages are always lines of characters terminated with a CR-LF | |
(Carriage Return - Line Feed) pair, and these messages shall not | |
exceed 512 characters in length, counting all characters including | |
the trailing CR-LF. Thus, there are 510 characters maximum allowed | |
for the command and its parameters. There is no provision for | |
continuation message lines. See section 7 for more details about | |
current implementations. | |
Most of the messages sent to the server generate a reply of some | |
sort. The most common reply is the numeric reply, used for both | |
errors and normal replies. The numeric reply must be sent as one | |
message consisting of the sender prefix, the three digit numeric, and | |
the target of the reply. A numeric reply is not allowed to originate | |
from a client; any such messages received by a server are silently | |
dropped. | |
The PASS command is used to set a 'connection password'. The | |
password can and must be set before any attempt to register the | |
connection is made. Currently this requires that clients send a PASS | |
command before sending the NICK/USER combination and servers *must* | |
send a PASS command before any SERVER command. The password supplied | |
must match the one contained in the C/N lines (for servers) or I | |
lines (for clients). It is possible to send multiple PASS commands | |
before registering but only the last one sent is used for | |
verification and it may not be changed once registered. Numeric | |
Replies: | |
ERR_NEEDMOREPARAMS ERR_ALREADYREGISTRED | |
Example: | |
PASS secretpasswordhere | |
The USER message is used at the beginning of connection to specify | |
the username, hostname, servername and realname of s new user. It is | |
also used in communication between servers to indicate new user | |
arriving on IRC, since only after both USER and NICK have been | |
received from a client does a user become registered. | |
Between servers USER must to be prefixed with client's NICKname. | |
Once a user has joined a channel, they receive notice about all | |
commands their server receives which affect the channel. This | |
includes MODE, KICK, PART, QUIT and of course PRIVMSG/NOTICE. The | |
JOIN command needs to be broadcast to all servers so that each server | |
knows where to find the users who are on the channel. This allows | |
optimal delivery of PRIVMSG/NOTICE messages to the channel. | |
If a JOIN is successful, the user is then sent the channel's topic | |
(using RPL_TOPIC) and the list of users who are on the channel (using | |
RPL_NAMREPLY), which must include the user joining. | |
Command: NAMES | |
Parameters: [{,}] | |
By using the NAMES command, a user can list all nicknames that are | |
visible to them on any channel that they can see. Channel names | |
which they can see are those which aren't private (+p) or secret (+s) | |
or those which they are actually on. The parameter | |
specifies which channel(s) to return information about if valid. | |
There is no error reply for bad channel names. | |
If no parameter is given, a list of all channels and their | |
occupants is returned. At the end of this list, a list of users who | |
are visible but either not on any channel or not on a visible channel | |
are listed as being on `channel' "*". | |
Numerics: | |
RPL_NAMREPLY RPL_ENDOFNAMES | |
Command: LIST | |
Parameters: [{,} []] | |
The list message is used to list channels and their topics. If the | |
parameter is used, only the status of that channel | |
is displayed. Private channels are listed (without their | |
topics) as channel "Prv" unless the client generating the query is | |
actually on that channel. Likewise, secret channels are not listed | |
at all unless the client is a member of the channel in question. | |
Numeric Replies: | |
ERR_NOSUCHSERVER RPL_LISTSTART | |
RPL_LIST RPL_LISTEND | |
Examples: | |
LIST ; List all channels. | |
Numeric Replies: | |
ERR_NOSUCHSERVER RPL_LISTSTART | |
RPL_LIST RPL_LISTEND | |
Useful message codes: | |
401 ERR_NOSUCHNICK | |
" :No such nick/channel" | |
- Used to indicate the nickname parameter supplied to a | |
command is currently unused. | |
402 ERR_NOSUCHSERVER | |
" :No such server" | |
- Used to indicate the server name given currently | |
doesn't exist. | |
403 ERR_NOSUCHCHANNEL | |
" :No such channel" | |
- Used to indicate the given channel name is invalid. | |
404 ERR_CANNOTSENDTOCHAN | |
" :Cannot send to channel" | |
- Sent to a user who is either (a) not on a channel | |
which is mode +n or (b) not a chanop (or mode +v) on | |
a channel which has mode +m set and is trying to send | |
a PRIVMSG message to that channel. | |
451 ERR_NOTREGISTERED | |
":You have not registered" | |
- Returned by the server to indicate that the client | |
must be registered before the server will allow it | |
to be parsed in detail. | |
461 ERR_NEEDMOREPARAMS | |
" :Not enough parameters" | |
- Returned by the server by numerous commands to | |
indicate to the client that it didn't supply enough | |
parameters. | |
431 ERR_NONICKNAMEGIVEN | |
":No nickname given" | |
- Returned when a nickname parameter expected for a | |
command and isn't found. | |
432 ERR_ERRONEUSNICKNAME | |
" :Erroneus nickname" | |
433 ERR_NICKNAMEINUSE | |
" :Nickname is already in use" | |
- Returned when a NICK message is processed that results | |
in an attempt to change to a currently existing | |
nickname. | |
351 RPL_VERSION | |
". :" | |
- Reply by the server showing its version details. | |
The is the version of the software being | |
used (including any patchlevel revisions) and the | |
is used to indicate if the server is | |
running in "debug mode". | |
The "comments" field may contain any comments about | |
the version or further version details. | |
353 RPL_NAMREPLY | |
" :[[@|+] [[@|+] [...]]]" | |
366 RPL_ENDOFNAMES | |
" :End of /NAMES list" | |
- To reply to a NAMES message, a reply pair consisting | |
of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the | |
server back to the client. If there is no channel | |
found as in the query, then only RPL_ENDOFNAMES is | |
returned. The exception to this is when a NAMES | |
message is sent with no parameters and all visible | |
channels and contents are sent back in a series of | |
RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark | |
the end. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment