Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
leedo committed Apr 17, 2009
0 parents commit 652cae8
Show file tree
Hide file tree
Showing 27 changed files with 1,406 additions and 0 deletions.
Empty file added README
Empty file.
189 changes: 189 additions & 0 deletions colloqueer.pl
@@ -0,0 +1,189 @@
#!/usr/bin/perl

use strict;
use warnings;

use Template;
use DateTime;
use MIME::Base64 qw/encode_base64/;
use Gtk2 -init;
use Gtk2::Gdk::Keysyms;
use Gtk2::WebKit;
use Glib qw/TRUE FALSE/;
use XML::LibXML;
use XML::LibXSLT;
use FindBin;
use lib "$FindBin::Bin/lib";
use POE qw/Component::IRC::State/;
use POE::Kernel { loop => 'Glib' };
use Encode;
use IPC::Open2;

my $url_re = q{\b(s?https?|ftp|file|gopher|s?news|telnet|mailbox):} .
q{(//[-A-Z0-9_.]+:\d*)?} .
q{[-A-Z0-9_=?\#:\$\@~\`%&*+|\/.,;\240]+};

my @channels = ('#onebutan','#cockes','#buttes');
my $nick = 'leedo';

my $irc = POE::Component::IRC->spawn(
nick => $nick,
port => 2227,
ircname => $nick,
server => 'irc.buttes.org',
) or die $!;

POE::Session->create(
package_states => [
main => [ qw/_start irc_001 irc_public/ ],
],
heap => {
parser => XML::LibXML->new(),
xslt => XML::LibXSLT->new(),
irc => $irc,
style => 'Buttesfire',
channels => {},
},
);

$poe_kernel->run();
exit 0;

sub irc_001 {
my ($heap, $sender) = @_[HEAP, SENDER];
my $irc = $sender->get_heap();
for (@channels) {
$irc->yield( join => $_);
setup_tab($heap, $_);
}
}

sub setup_tab {
my ($heap, $channel) = @_;
my $paned = Gtk2::VPaned->new;
my $frame = Gtk2::Frame->new;
my $wv = Gtk2::WebKit::WebView->new;
my $entry = Gtk2::Entry->new;
$heap->{channels}{$channel}{msgs} = [];
$entry->signal_connect("key_press_event", sub {
my ($widget, $event) = @_;
if ($event->keyval == $Gtk2::Gdk::Keysyms{Return}) {
$irc->yield(privmsg => $channel => $widget->get_text);
push @{$heap->{channels}{$channel}{msgs}}, {
nick => $nick,
text => irc2html($widget->get_text),
date => DateTime->now,
id => encode_base64($nick.time)
};
$widget->set_text('');
refresh_channel($channel, $heap);
}
});
$frame->add($wv);
$paned->pack1($frame, TRUE, FALSE);
$paned->pack2($entry, FALSE, FALSE);
$heap->{notebook}->append_page($paned, $channel);
$heap->{channels}{$channel}{view} = $wv;
$heap->{main_window}->show_all;
}

sub irc_public {
my ($heap, $sender, $who, $where, $what) = @_[HEAP, SENDER, ARG0 .. ARG2];
my $from = ( split /!/, $who )[0];
my $channel = $where->[0];
return unless exists $heap->{channels}{$channel};

push @{$heap->{channels}{$channel}{msgs}}, {
nick => $from,
text => irc2html($what),
date => DateTime->now,
id => encode_base64($from.time)
};
refresh_channel($channel, $heap);
}

sub refresh_channel {
my ($channel, $heap) = @_;
my (@msg_out, @buff);
my @msg_in = @{$heap->{channels}{$channel}{msgs}};
my $lastnick = $msg_in[0]->{nick};
for my $index (0 .. $#msg_in) {
my $msg = $msg_in[$index];
$msg->{id} =~ s/=*\n//; # strip trailing mime crud
if ($msg->{nick} ne $lastnick) {
push @msg_out, format_messages($heap, @buff);
@buff = ();
}
push @buff, $msg;
$lastnick = $msg->{nick};
}
push @msg_out, format_messages($heap, @buff);
$heap->{tt}->process('base.html', {msgs => \@msg_out}, \(my $html));
$heap->{channels}{$channel}{view}->load_html_string($html, '/');
}

sub format_messages {
my ($heap, @msgs) = @_;
my $from = $msgs[0]->{nick};
$heap->{tt}->process('message.xml', {
user => $from,
msgs => \@msgs,
self => $from eq $nick ? 1 : 0,
}, \(my $message)) or die $!;
my $doc = $heap->{parser}->parse_string($message,{encoding => 'utf8'});
my $results = $heap->{style_doc}->transform($doc,
XML::LibXSLT::xpath_to_string(
consecutiveMessage => 'no',
fromEnvelope => 'yes',
bulkTransform => "yes",
));
$message = $heap->{style_doc}->output_string($results);
$message =~ s/<span[^\/>]*\/>//gi; # strip empty spans
return decode_utf8($message);
}

sub irc2html {
my $string = shift;
my $pid = open2 my $out, my $in, "ruby $FindBin::Bin/irc2html.rb";
print $in $string;
close $in;
$string = <$out>;
$string =~ s/\s{2}/ &#160;/g;
$string =~ s@($url_re+)@<a href="$1">$1</a>@gi;
close $out;
chomp $string;
wait;
return $string;
}

sub _start {
my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];

$heap->{share_dir} = "$FindBin::Bin/share";
$heap->{theme_dir} = $heap->{share_dir} . '/styles/'
. $heap->{style} . ".colloquyStyle/Contents/Resources";

$heap->{tt} = Template->new(
ENCODING => 'utf8',
INCLUDE_PATH => [$heap->{share_dir}, $heap->{theme_dir}]);

$heap->{style_doc} = $heap->{xslt}->parse_stylesheet(
$heap->{parser}->parse_file($heap->{theme_dir}."/main.xsl"));

$heap->{tt}->process('base.html',undef,\(my $html))
or die $heap->{tt}->error;

$heap->{main_window} = Gtk2::Window->new('toplevel');
$heap->{main_window}->set_default_size(500,400);
$heap->{main_window}->set_border_width(0);
$kernel->signal_ui_destroy( $heap->{main_window} );
$heap->{notebook} = Gtk2::Notebook->new;
$heap->{notebook}->set_show_tabs(TRUE);
$heap->{notebook}->set_tab_pos('bottom');
$heap->{main_window}->add($heap->{notebook});
$heap->{main_window}->show_all;

my $irc = $heap->{irc};
$irc->yield( register => 'all' );
$irc->yield( connect => { } );
}
126 changes: 126 additions & 0 deletions irc2html.rb
@@ -0,0 +1,126 @@
#!/usr/bin/env ruby

require "enumerator"
require "cgi"

module Kernel
def returning(value)
yield value
value
end
end

class Regexp
def uncapturize
self.class.new(to_s.gsub(/(\A|[^\\])\(([^?])/, "\\1(?:\\2"))
end
end

module Enumerable
def in_groups_of(number, fill_with = nil, &block)
if fill_with == false
collection = self
else
padding = (number - size % number) % number
collection = dup.concat([fill_with] * padding)
end

if block_given?
collection.each_slice(number, &block)
else
returning [] do |groups|
collection.each_slice(number) { |group| groups << group }
end
end
end
end

module IrcFormatting
BOLD = "\002"
COLOR = "\003"
RESET = "\017"
INVERSE = "\026"
UNDERLINE = "\037"

COLOR_SEQUENCE = /(\d{0,2})(?:,(\d{0,2}))?/
FORMAT_SEQUENCE = /(
#{BOLD}
| #{COLOR}#{COLOR_SEQUENCE.uncapturize}?
| #{RESET}
| #{INVERSE}
| #{UNDERLINE}
)/x

COLORS = %w( #fff #000 #008 #080 #f00 #800 #808 #f80
#ff0 #0f0 #088 #0ff #00f #f0f #888 #ccc )

class Formatting < Struct.new(:b, :i, :u, :fg, :bg)
def dup
self.class.new(*values)
end

def reset!
self.b = self.i = self.u = self.fg = self.bg = nil
end

def accumulate(format_sequence)
returning dup do |format|
case format_sequence[/^./]
when BOLD then format.b = !format.b
when UNDERLINE then format.u = !format.u
when INVERSE then format.i = !format.i
when RESET then format.reset!
when COLOR then format.fg, format.bg = extract_colors_from(format_sequence)
end
end
end

def to_css
css_styles.map do |name, value|
"#{name}: #{value}"
end.join("; ")
end

protected
def extract_colors_from(format_sequence)
fg, bg = format_sequence[1..-1].scan(COLOR_SEQUENCE).flatten
if fg.empty?
[nil, nil]
else
[fg.to_i, bg ? bg.to_i : self.bg]
end
end

def css_styles
returning({}) do |styles|
fg, bg = i ? [self.bg || 0, self.fg || 1] : [self.fg, self.bg]
styles["color"] = COLORS[fg] if fg
styles["background-color"] = COLORS[bg] if bg
styles["font-weight"] = "bold" if b
styles["text-decoration"] = "underline" if u
end
end
end

def self.parse_formatted_string(string)
returning [""].concat(string.split(FORMAT_SEQUENCE)).in_groups_of(2) do |split_text|
formatting = Formatting.new
split_text.map! do |format_sequence, text|
formatting = formatting.accumulate(format_sequence)
[formatting, text]
end
end
end

def self.formatted_string_to_html(string)
string.split("\n").map do |line|
parse_formatted_string(line).map do |formatting, text|
"<span style=\"#{formatting.to_css}\">#{CGI.escapeHTML(text || "")}</span>"
end.join
end.join("\n")
end
end

if __FILE__ == $0
puts "#{IrcFormatting.formatted_string_to_html($<.read)}"
end
58 changes: 58 additions & 0 deletions share/base.html
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/ecmascript" defer="defer" encoding="utf-8" src="roomTopicBanner.js"></script>
<style type="text/css">
html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
body {
height: 100%;
width: 100%;
display: block;
margin: 0;
padding: 0;
border: none;
border-spacing: 0;
overflow: hidden;
}

.banner {
display: table-row;
background-color: white;
}

.banner > * {
display: table-cell;
border: none;
vertical-align: top;
}
div#content {
width: 100%;
height: 100%;
overflow-x: none;
overflow-y: scroll;
}
[% INCLUDE default.css %]
[% INCLUDE main.css %]
</style>
<script type="text/javascript">
[% INCLUDE default.js %]
</script>
</head>
<body>
<div id="content"><div style="padding:4px 0 5px 0">[% FOR msg IN msgs %][% msg %][% END %]</div></div>
[% INCLUDE genericTemplate.html %]
<script type="text/javascript">
scrollToBottom();
setTimeout(scrollToBottom, 50);
</script>
</body>
</html>

0 comments on commit 652cae8

Please sign in to comment.