Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 652cae8
Showing
27 changed files
with
1,406 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}/  /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 => { } ); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
|
Oops, something went wrong.