From 6f9873733191ef7b27c263b86c824d677833281c Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 2 Dec 2013 12:16:44 -0800 Subject: [PATCH] Initial commit for PCCC 3.0 --- CHANGES.txt | 171 ++ PCCC.pl | 3522 ++++++++++++++++++++++++++++++++++++++ README.md | 140 ++ balloon.gif | Bin 0 -> 726 bytes docs/404.html | 13 + docs/about.html | 27 + docs/action.html | 36 + docs/colors.html | 33 + docs/connection.html | 36 + docs/console.html | 26 + docs/cyan.html | 25 + docs/details.html | 15 + docs/general.html | 82 + docs/ignore.html | 24 + docs/ignorelist.html | 59 + docs/index.html | 41 + docs/misc.html | 73 + docs/pm.html | 50 + docs/profile.html | 27 + docs/raw.html | 29 + docs/rules.html | 44 + docs/sounds.html | 56 + docs/transcript.html | 32 + docs/usage.html | 65 + lib/Net/CyanChat.pm | 696 ++++++++ lib/Net/CyanChat.pm~ | 700 ++++++++ lib/Tk/HyperText.pm | 792 +++++++++ lib/Win32/MediaPlayer.pm | 253 +++ sfx/ding.wav | Bin 0 -> 76972 bytes sfx/link.wav | Bin 0 -> 100383 bytes sfx/message.wav | Bin 0 -> 4605 bytes web.gif | Bin 0 -> 1577 bytes worlds.gif | Bin 0 -> 1936 bytes worlds.ico | Bin 0 -> 15086 bytes worlds.png | Bin 0 -> 1944 bytes 35 files changed, 7067 insertions(+) create mode 100644 CHANGES.txt create mode 100755 PCCC.pl create mode 100644 README.md create mode 100644 balloon.gif create mode 100644 docs/404.html create mode 100644 docs/about.html create mode 100644 docs/action.html create mode 100644 docs/colors.html create mode 100644 docs/connection.html create mode 100644 docs/console.html create mode 100644 docs/cyan.html create mode 100644 docs/details.html create mode 100644 docs/general.html create mode 100644 docs/ignore.html create mode 100644 docs/ignorelist.html create mode 100644 docs/index.html create mode 100644 docs/misc.html create mode 100644 docs/pm.html create mode 100644 docs/profile.html create mode 100644 docs/raw.html create mode 100644 docs/rules.html create mode 100644 docs/sounds.html create mode 100644 docs/transcript.html create mode 100644 docs/usage.html create mode 100644 lib/Net/CyanChat.pm create mode 100644 lib/Net/CyanChat.pm~ create mode 100644 lib/Tk/HyperText.pm create mode 100644 lib/Win32/MediaPlayer.pm create mode 100644 sfx/ding.wav create mode 100644 sfx/link.wav create mode 100644 sfx/message.wav create mode 100644 web.gif create mode 100644 worlds.gif create mode 100644 worlds.ico create mode 100644 worlds.png diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..8938f9e --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,171 @@ ++--------------------------+ +| Perl CyanChat Client 2.x | ++--------------------------+ + +3.0 Jun 21 2007 + - Made some changes to the Debug Window: + - The output filehandles (STDOUT; STDERR) are no longer bound to this window; they're + sent to the terminal (if present) like default. + - Only CC packets are displayed in the Debug Window. Server packets are in blue text, + and client packets are in red. + - Changed the default Special Guest color back to orange, and the action color back + to yellow. + - Built in support for "profiles." Your configuration, ignore lists, and chat logs + are now saved in your home directory instead of in PCCC's directory. So on Linux + this means /home/username/.pccc, and WinXP is C:/Documents and Settings/user/PCCC + - Made a minor edit to the Net::CyanChat library, so that it can detect when the + server has banned you from the room. + - Added support for sound effects. It uses the Win32::MediaPlayer module on Windows, + or the system command `play` on Linux. + - Added a "Force Quit" command under the File Menu. It's disabled by default. When + you attempt at least once to exit the program properly, and it for whatever reason + fails to exit, the Force Quit option becomes enabled. Alternatively, the keyboard + shortcut Ctrl+Alt+Q will kill the program. + - Moved the configuration options for MutualIgnore, LoudIgnore, and SendIgnore to + the bottom of the "Ignored Users" tab. + - Created a "Sounds" tab, with configurable options for: + - Enable sounds -- disable this, and all sounds are disabled. + - Play sounds on certain events... + - When a user joins the room + - When a user exits the room + - When a public message is received + - When a private message is received + - Added a "Mute sounds" option to the bottom of the Chat menu. This option will + temporarily disable sounds, but not permanently save this state to your config + file. + - Changed how action messages ("/me") and typo messages appear. The new style is: + Action Messages: ** Nickname performs an action ** + Typo Messages: [Nickname] *their typo correction + All the text is in the action text color except for the nickname, and in the case + of typo messages, the brackets around the nick. In typo corrections, the user's exact + message is shown as usual, just in yellow text instead of silver. + - Reprogrammed the entire help system. It now uses Tk::HyperText and renders HTML + documents from the "docs" folder. Also, added an "About" menu option to the Help menu, + which opens the appropriate page in the Help Viewer. Also, "Help" buttons on the + Preferences window will load the appropriate page in the Help Viewer too. + - Bug fix: when copying/pasting text from an outside source (e.g. from a web page) + into the typing space, the newline characters would be preserved when they shouldn't + be. Sending the message would result in getting banned from Cho. This has been fixed + now, as the characters \x0d (Cr) and \x0a (Lf) are filtered out of your message. + - Bug fix: got rid of the right-click context menus on the Who List. For Windows users, + right-clicking and bringing up this menu would pause the main program loop, effectively + preventing PCCC from polling the server for new events. The GUI would still work just fine, + but the loop wouldn't work anymore. + +2.8 Jun 1 2007 + - Removed the "highlight borders" on the widgets, so that Linux and Mac users don't + have to see those ugly borders around i.e. the "Autoscroll" check box, as well as + buttons and text boxes. + - Redesigned the preferences window. + - Added the option of *not* showing private messages in new IM windows. When the + option is disabled, private messages only show up in IM windows if an IM window + already exists, and the only way to create an IM window is to double-click a name + in the Who List. + - Added right-click context menus to the Who List. Right-clicking a user displays a + context menu along the lines of: + Username:address + ---------------- + Send private message + Ignore user + ... or "Unignore user" if you already ignored them. + - Added an "Ignored Users" tab to the Preferences window, where you can view your + ignore list, adding or removing users if necessary. + - Added "Notifications" -- when a new message arrives (in public chat or private + message windows), and the window is out of focus or minimized, the window title + will animate to get your attention. + - Added an "Auto-logging" option, which will automatically log all messages received + in chat. It saves them into "./logs/yyyy-mm-dd/yyyymmdd-x.html", where yyyymmdd is a + date stamp, and x is a session number starting from 1, which increments each time PCCC + is run. + - Bug Fixes: + - Fixed the "disappearing name" bug (where you'd log in to chat, open the Preferences + window, hit Cancel, and your nick in the Name: box would revert to the "default nick" + from the preferences, which is blank by default). + - Added configuration options: + - TimeStamps: show time stamps on all messages. + - IMWindows: show private messages in new "IM" windows + +2.7 May 23 2007 + - The client now assumes "htmlview" as the default "Browser Command" when you're not + on Windows and there is no config file yet. Otherwise, the default is "start" + +2.6 Apr 14 2007 + - Added the "Browser Command" option, to specify the console command used to open your + web browser. Windows users can just leave this as "start", but Linux users will have + to specify "firefox", "mozilla", or another command. + - Added a "Reverse Orientation" option. Users that are familiar with most traditional + chat programs, in which the message typing space is below the conversation space, + will want to enable this option (in conjunction with unchecking "Reverse chat dialog"). + - Made the Preferences and Enter Raw Command windows a little bit bigger. The "Ok" + button was being squished on Linux, and the Enter Raw Command's buttons weren't even + visible before. + - Updated the help file with information about the new options added. + +2.5 Mar 1 2007 + - Two more Frame widgets added to the user interface. Now the window "scales" better + (when you maximize the window, the input box stays at the top and the dialog window + stretches to fill all the remaining space; previously, the dialog window and input + box would fight for the new space, causing a lot of unnecessary padding above and below + the input box). + - Added hyperlinking support for the main chat dialog window (but not yet for private + message windows). + +2.4 Jan 29 2007 + - Added a checkbutton to enable/disable the automatic scrolling of the chat window when + new messages are received. + - The "Disconnect" menu option is disabled from the start if you are not connected yet, + like it should've been. + - The Who List gets wiped clean when you disconnect from the server. The lack of doing this + used to cause problems where people in the Who List weren't actually in the chat room, + so clearing the list fixes this problem. + - Made sure I don't forget to include the latest version of Net::CyanChat in the source + distribution this time. ;) + +2.3 Nov 7 2006 + - Minor bug fixes. + +2.2 Oct 30 2006 + - Added more color variables: the main window background/foreground and the button + background/foreground is now configurable separate from the rest of the screen. + The WhoList can have a different background than the dialog window. + - The "Save Transcript" now saves the conversation as XHTML, keeping the colors of the chat. + If you save it to a text file, it ignores the formatting (the old behavior of PCCC). + - Action messages have changed display formats: + Old Way: *** [username] action *** + New Way: [username] action in yellow ("action") text color + - Outgoing private messages are echoed in the chat dialog window, even if you sent them in + a separate PM window. + - Added some new configuration options: + - AutoAct: when a message starts and ends with *'s, it will be treated like a /me action. + - LoudTypo: when a message starts with a * (typically for typo corrections), a notification + will be shown about the typo being corrected. + - Added a full documentation system. Click "Help -> Contents" + - Bug fixes: + - Private message windows now auto-scroll. + +2.1 Oct 24 2006 + - Added new configuration options: + - AutoJoin: automatically join the room on connect (if Nickname has a length) + - BlockServer: ignore private messages from ChatServer (when on debug port 1813) + - LoudIgnore: show a notification when somebody blocks you + - SendIgnore: send the ignore command to the server when you ignore somebody + - IgnoreBack: perform a mutual ignore when ignored (ignore the one ingoring you) + - Added the ability to reset the configuration to the defaults. + - The entire window now recolors itself when you change color settings (rather than just + the conversation window) + - A disconnect handler has been added so the client knows when you've been disconnected + from the chat server. + - Added the ability to automatically reconnect on disconnect. This functionality is limited + though. It won't keep trying. But this will solve the quick temporary disconnects experienced + on wireless Internet connections. + - A few bugs have been fixed: + - The Preferences window now has a fixed default width and height. Previously, it + was leaving it up to the window contents to automatically adjust its size, but this + didn't work on *nix platforms and the window was too small. + - The "Connection Details" window has a fixed default width and height too, for same reasons. + - Private Message windows will come back now. Previously, if you opened a private message + window with somebody, then closed that window, you couldn't reopen it (unless the other + person sent you a message to open the window). + +2.0 Oct 1 2006 + - Initial release. diff --git a/PCCC.pl b/PCCC.pl new file mode 100755 index 0000000..73c7f6d --- /dev/null +++ b/PCCC.pl @@ -0,0 +1,3522 @@ +#!/usr/bin/perl -w + +# Perl CyanChat Client 3.0 - A complete rewrite from PCCC 1.x +# (C) 2006-07 Casey Kirsle + +use strict; +use warnings; +use threads; +use threads::shared; + +# Spawn a dedicated hyperlink-launching thread so we don't +# freeze up the MainWindow while a webpage loads. +our @HYPERLINKLIST : shared; +@HYPERLINKLIST = (); +our $HTTPBROWSER : shared; +our $linkthread = threads->create (sub { + print "LinkerThread activated.\n"; + while (1) { + select (undef,undef,undef,0.01); + if (@HYPERLINKLIST) { + my $next = shift @HYPERLINKLIST; + + print "Hyperlink: $next\n"; + + if ($next eq "+shutdown") { + # Shut things down. + print "shutting down link thread\n"; + last; + } + + system ("$HTTPBROWSER $next"); + } + } +}); + +# Spawn a dedicated sound effect playing thread. +our @PLAYSOUNDS : shared; +@PLAYSOUNDS = (); +our $MEDIAPLAYER : shared; +$MEDIAPLAYER = undef; +our $mediathread = threads->create (sub { + print "MediaThread activated.\n"; + + # If this is on Windows, create the media player. + my $win32mplayer = undef; + if ($^O =~ /win(32|64)/i) { + require Win32::MediaPlayer; + } + while (1) { + select (undef,undef,undef,0.01); + if (@PLAYSOUNDS) { + my $next = shift @PLAYSOUNDS; + + print "MPlayer: $next\n"; + + if ($next eq "+shutdown") { + # Shut this thread down. + print "shutting down media player\n"; + last; + } + + # If the play command is undef, we might be on Windows. + if (not defined $MEDIAPLAYER) { + # To see for sure we're on Windows, + # win32mplayer should have a value. + $win32mplayer = new Win32::MediaPlayer; + $win32mplayer->load ("./sfx/$next"); + $win32mplayer->play; + } + else { + # Send this directly to the play command. + system ("$MEDIAPLAYER ./sfx/$next"); + } + } + } +}); + +use lib "./lib"; +use Net::CyanChat; +use Tk; +use Tk::ROText; +use Tk::NoteBook; +use Tk::LabFrame; +use Tk::Pane; +use Tk::Dialog; +use Tk::Balloon; +use Tk::BrowseEntry; +use Tk::HyperText; + +our $MODIFIED = '21 June 2007'; +our $VERSION = '3.0'; # Program Version +our $mw = undef; # MainWindow Object +our %IMAGE = (); # Image Objects +our %menu = (); # Menu Items +our %config = (); # Configuration Data +our $homedir = '.'; # Home directory +our $FONT = []; # Reusable font definitions. +our $chat = undef; # Chat Dialog Object +our $wholist = undef; # WhoList Object +our $adminlist = undef; # Cyan Staff & Guests WhoList +our $connected = 0; # Not Connected +our $loggedin = 0; # Not Logged In +our $mutesfx = 0; # Temporarily mute all sounds +our $netcc = undef; # Net::CyanChat object. +our %online = (); # Online Users List +our %ignore = (); # Ignore List +our %windows = (); # Keep track of child windows. +our %private = (); # Private text widgets. +our %pmsg = (); # Private message variables. +our %pfocus = (); # focus status on private msg windows +our @xhtml = (); # keeps xhtml version for logging +our $dbgtext = undef; # Debug messages text widget +our $tipper = undef; # Tooltip balloon object +our %user = ( # Personal user stuff. + nick => '', + msg => '', +); +our $pOnlineList = undef; # Preferences/Ignore - online users +our $pIgnoreList = undef; # Preferences/Ignore - ignored users +our $hyperlink = 0; # Hyperlink ID incrementer +our $notification = [ # Window notification animation + [ '>', '<' ], + [ '>>', '<<' ], + [ '>>>', '<<<' ], + [ '', '' ], + #[ '==>', '<==' ], + #[ '===', '===' ], + #[ '>==', '==<' ], + #[ '=>=', '=<=' ], + #[ '>=>', '<=<' ], + #[ '=>=', '=<=' ], +]; +our $winanim = {}; # Window animation phases. +our $autologid = 0; # ID to stick with for autologging. +our $htmlhelp = undef; # Help page HTML widget +our @helphistory = (); # Help page history +our $helpPage = "index.html"; +our $controlFrame = undef; +our $mainFrame = undef; +our $rightFrame = undef; +our $btnFrame = undef; +our $whoFrame = undef; +our $chatFrame = undef; +our $msgboxFrame = undef; +our $dialogFrame = undef; + +############################################ +## Initialization ## +############################################ +&init(); + +sub init { + # Detect operating systems. + &initOS(); + + # Load configuration. + &initConfig(); + + # Draw the GUI. + &initGUI(); + + # Run the main loop. + &loop(); +} + +sub initOS { + # Find our operating system. + my $os = $^O; + + print "Detecting your OS... $os\n"; + + my $homename = '.pccc'; + if ($os =~ /win(32|64)/i) { # Microsoft Windows + # HTTP Browser command = `start` by default. + # MediaPlayer = undef (use Win32::MediaPlayer instead) + $HTTPBROWSER = "start"; + $MEDIAPLAYER = undef; + $homename = "PCCC"; + } + elsif ($os =~ /linux/i || $os =~ /unix/i) { # Linux, probably + # HTTP Browser command = `htmlview` by default. + # MediaPlayer = `play` by default. + $HTTPBROWSER = "htmlview"; + $MEDIAPLAYER = "play"; + $homename = ".pccc"; + } + else { + # Unknown OS (possibly Mac), use the same defaults as Linux. + $HTTPBROWSER = "htmlview"; + $MEDIAPLAYER = "play"; + $homename = ".pccc"; + } + + # Detect our home directory. + my $home = $ENV{HOME} || $ENV{HOMEDIR} || $ENV{USERPROFILE} || ''; + $home =~ s~\\~/~g; # Fix Win32 paths. + + print "Detecting your home directory... $home\n"; + + # If we have one... + if (length $home) { + # See if PCCC has a folder. + if (!-d "$home/$homename") { + # No. Make it. + print "Making home directory $home/$homename\n"; + mkdir ("$home/$homename") or warn "Can't create config directory at " + . "$home/$homename: $!"; + } + + # Now if it does... + if (-d "$home/$homename") { + # Set this as our home directory. + print "Setting home directory to $home/$homename\n"; + $homedir = "$home/$homename"; + } + } +} + +sub initGUI { + # Create a Tk MainWindow. + our $mw = MainWindow->new ( + -title => "Perl CyanChat Client", + ); + $mw->geometry ('640x480'); + $mw->optionAdd ('*tearOff','false'); + $mw->optionAdd ('*highlightThickness','0'); + $mw->protocol ('WM_DELETE_WINDOW', \&shutdown); + + # Load application icons. + foreach (qw(worlds web balloon)) { + $IMAGE{$_} = $mw->Photo (-file => "./$_\.gif", -format => 'GIF', -width => 32, -height => 32); + } + + # Set the appicon. + $mw->Icon (-image => $IMAGE{worlds}); + + # Create the tooltip object. + $tipper = $mw->Balloon ( + -balloonposition => 'mouse', + -foreground => '#000000', + -background => '#FFFFCC', + ); + + # Setup the notification animation states. + $winanim->{__mainwindow__} = { + title => 'Perl CyanChat Client', + focused => -1, + animating => 0, + phase => 0, + proceed => 0, + }; + $mw->bind ('', sub { + $winanim->{__mainwindow__}->{focused} = 1; + &animReset("__mainwindow__"); + }); + $mw->bind ('', sub { + $winanim->{__mainwindow__}->{focused} = 0; + }); + + # Create the debugging window (which shows all packets) + $windows{__debug__} = $mw->Toplevel ( + -title => 'Debug Window', + ); + $windows{__debug__}->geometry ('320x240'); + $windows{__debug__}->Icon (-image => $IMAGE{web}); + $windows{__debug__}->withdraw; + $windows{__debug__}->protocol ('WM_DELETE_WINDOW', sub { + return 0; + }); + my $dbgBtm = $windows{__debug__}->Frame->pack (-fill => 'x', -side => 'bottom'); + my $dbgTop = $windows{__debug__}->Frame->pack (-fill => 'both', -expand => 1, -side => 'top'); + + # Create the debug window's text viewer. + $dbgtext = $dbgTop->Scrolled ('ROText', + -scrollbars => 'e', + -foreground => '#000000', + -background => '#FFFFFF', + -wrap => 'word', + -font => [ + -family => 'Courier New', + -size => 10, + ], + )->pack (-fill => 'both', -expand => 1); + $dbgtext->tagConfigure ("server", -foreground => '#0000FF'); + $dbgtext->tagConfigure ("client", -foreground => '#FF0000'); + my $realtext = $dbgtext->Subwidget ('rotext'); + + # Create the debug window buttons. + $dbgBtm->Button ( + -text => 'Dismiss', + -command => sub { + $windows{__debug__}->withdraw; + }, + )->grid (-column => 0, -row => 0, -padx => 10, -pady => 2); + $dbgBtm->Button ( + -text => 'Clear', + -command => sub { + $dbgtext->delete ('0.0','end'); + }, + )->grid (-column => 1, -row => 0, -padx => 10, -pady => 2); + + # Create the menu bar. + $menu{master} = $mw->Menu ( + -type => 'menubar', + ); + $mw->configure (-menu => $menu{master}); + + $menu{filemenu} = $menu{master}->cascade ( + -label => '~File', + ); + + $menu{filemenu}->command (-label => '~Save Transcript', -accelerator => 'Ctrl+S', -command => sub { + #print "xhtml\n" . join ("\n",@xhtml); + my $file = $mw->getSaveFile ( + -initialdir => '.', + -defaultextension => '.html', + -filetypes => [ + [ 'HTML Document', '*.html' ], + [ 'Text Document', '*.txt' ], + [ 'All Files', '*.*' ], + ], + ); + + return unless defined $file; + + if ($file =~ /\.txt$/i) { + # Save as plain text. + open (SAVE, ">$file"); + print SAVE $chat->get('1.0','end'); + close (SAVE); + } + else { + # Save as HTML. + &saveHTML ($file); + } + }); + + $menu{filemenu}->command (-label => '~Clear Chat', -command => sub { + # Do autologging first. + &doAutolog(); + + # Reset our autolog ID (start a new session) + $autologid = 0; + + # Clear the chat and XHTML buffer. + $chat->see ('0.0'); + $chat->delete ('0.0','end'); + @xhtml = (); + }); + + $menu{filemenu}->separator; + + $menu{forcequit} = $menu{filemenu}->command (-label => '~Force Quit', -accelerator => 'Ctrl+Alt+Q', + -state => 'disabled', -command => sub { + exit(0); + }); + + $menu{filemenu}->command (-label => '~Exit', -accelerator => 'Alt+F4', -command => sub { + &shutdown(); + }); + + $menu{editmenu} = $menu{master}->cascade ( + -label => '~Edit', + ); + + $menu{editmenu}->command (-label => '~Copy', -accelerator => 'Ctrl+C', -command => sub { + $chat->Column_Copy_or_Cut (0); + }); + + $menu{editmenu}->command (-label => '~Find...', -accelerator => 'Ctrl+F', -command => sub { + $chat->findandreplacepopup (1); + }); + + $menu{editmenu}->command (-label => '~Select All', -accelerator => 'Ctrl+A', -command => sub { + $chat->selectAll(); + }); + + $menu{editmenu}->command (-label => '~Unselect All', -command => sub { + $chat->unselectAll(); + }); + + $menu{chatmenu} = $menu{master}->cascade ( + -label => '~Chat', + ); + + $menu{connect} = $menu{chatmenu}->command (-label => '~Connect', -command => sub { + &connect(); + }); + $menu{disconnect} = $menu{chatmenu}->command (-label => '~Disconnect', -state => 'disabled', -command => sub { + &disconnect(); + }); + + $menu{details} = $menu{chatmenu}->command (-label => 'Connection Detail~s', -state => 'disabled', -command => sub { + if (exists $windows{__condetails__}) { + $windows{__condetails__}->focusForce; + } + else { + $windows{__condetails__} = $mw->Toplevel ( + -title => 'Connection Details', + ); + $windows{__condetails__}->Icon (-image => $IMAGE{web}); + $windows{__condetails__}->bind ('', sub { + delete $windows{__condetails__}; + }); + + my $serv = '(Custom)'; + my $port = '(Custom)'; + + if ($config{chathost} eq 'cho.cyan.com') { + $serv = '(Cyan Worlds)'; + } + if ($config{chatport} == 1812) { + $port = '(Default)'; + } + elsif ($config{chatport} == 1813) { + $port = '(Testing)'; + } + + $windows{__condetails__}->Label ( + -text => 'Chat Server:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 0, -sticky => 'e'); + + $windows{__condetails__}->Label ( + -textvariable => \$config{chathost}, + -font => $FONT, + )->grid (-column => 1, -row => 0, -sticky => 'w'); + + $windows{__condetails__}->Label ( + -textvariable => \$serv, + -font => $FONT, + )->grid (-column => 2, -row => 0, -sticky => 'w'); + + $windows{__condetails__}->Label ( + -text => 'Chat Port:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 1, -sticky => 'e'); + + $windows{__condetails__}->Label ( + -textvariable => \$config{chatport}, + -font => $FONT, + )->grid (-column => 1, -row => 1, -sticky => 'w'); + + $windows{__condetails__}->Label ( + -textvariable => \$port, + -font => $FONT, + )->grid (-column => 2, -row => 1, -sticky => 'w'); + + $windows{__condetails__}->Label ( + -text => 'Status:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 2, -sticky => 'e'); + + $windows{__condetails__}->Label ( + -text => 'Connected.', + -font => $FONT, + )->grid (-column => 1, -row => 2, -sticky => 'w'); + + $windows{__condetails__}->Button ( + -text => 'Close', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + -command => sub { + $windows{__condetails__}->destroy; + }, + )->grid (-column => 0, -columnspan => 3, -row => 3, -sticky => 'n'); + + $windows{__condetails__}->focusForce; + } + }); + + $menu{chatmenu}->separator; + + $menu{chatmenu}->command (-label => '~Open Console', -command => sub { + $windows{__debug__}->deiconify; + $dbgtext->see ('end'); + }); + + $menu{rawmenu} = $menu{chatmenu}->command (-label => '~Send Raw Command', -state => 'disabled', -command => sub { + my $win = $mw->Toplevel ( + -title => 'Send Raw Command', + ); + $win->geometry ('400x100'); + $win->Icon (-image => $IMAGE{web}); + + $win->Label ( + -text => "Use this tool to send a raw command directly to CyanChat.\n" + . "Only use this if you know what you're doing. If you get banned\n" + . "from CyanChat for sending a bad command, it's not my fault.", + )->pack; + my $packet = ''; + $win->Entry ( + -textvariable => \$packet, + )->pack (-fill => 'x'); + + my $frame = $win->Frame->pack (-fill => 'x'); + + $frame->Button ( + -text => 'Spawn Debug Window', + -command => sub { + $windows{__debug__}->deiconify; + $dbgtext->see ('end'); + }, + )->pack (-side => 'left'); + + $frame->Button ( + -text => 'Send Command', + -command => sub { + $netcc->send ($packet); + $packet = ''; + }, + )->pack (-side => 'left'); + + $frame->Button ( + -text => 'Close', + -command => sub { + $win->destroy; + }, + )->pack (-side => 'left'); + }); + + $menu{chatmenu}->separator; + + $menu{chatmenu}->command (-label => '~Preferences', -accelerator => 'F3', -command => sub { + &prefs(); + }); + + $menu{chatmenu}->separator; + + $menu{chatmenu}->checkbutton ( + -label => '~Mute Sounds', + -variable => \$mutesfx, + -onvalue => 1, + -offvalue => 0, + ); + + $menu{helpmenu} = $menu{master}->cascade ( + -label => '~Help', + ); + + $menu{helpmenu}->command (-label => '~About PCCC', -command => sub { + &help("about.html"); + }); + + $menu{helpmenu}->command (-label => '~Contents', -accelerator => 'F1', -command => sub { + &help(); + }); + + $menu{helpmenu}->separator; + + $menu{linkmenu} = $menu{helpmenu}->cascade ( + -label => '~Links', + ); + + $menu{linkmenu}->command (-label => '~PCCC Homepage', -command => sub { + push (@HYPERLINKLIST, "http://www.cuvou.com/?module=pccc"); + }); + + $menu{linkmenu}->command (-label => '~SourceForge Project Page', -command => sub { + push (@HYPERLINKLIST, "http://www.sourceforge.net/projects/perlccc"); + }); + + $menu{linkmenu}->command (-label => '~Cuvou.com', -command => sub { + push (@HYPERLINKLIST, "http://www.cuvou.com/"); + }); + + $menu{linkmenu}->separator; + + $menu{linkmenu}->command (-label => 'CyanChat ~Homepage', -command => sub { + push (@HYPERLINKLIST, "http://cho.cyan.com/chat/"); + }); + + $menu{linkmenu}->command (-label => 'CC P~rogrammers', -command => sub { + push (@HYPERLINKLIST, "http://cho.cyan.com/chat/programmers.html"); + }); + + $menu{linkmenu}->command (-label => 'Cyan ~Worlds', -command => sub { + push (@HYPERLINKLIST, "http://www.cyanworlds.com/"); + }); + + $menu{linkmenu}->separator; + + $menu{linkmenu}->command (-label => 'CC ~Quote Database', -command => sub { + push (@HYPERLINKLIST, "http://cyanchat.dnijazzclub.com/"); + }); + + # Create the layout Frames. + $controlFrame = $mw->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'top', -fill => 'x'); + $mainFrame = $mw->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'top', -fill => 'both', -expand => 1); + $rightFrame = $mainFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'right', -fill => 'y'); + $btnFrame = $rightFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'x'); + $whoFrame = $rightFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'both', -expand => 1); + + $chatFrame = $mainFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'right', -fill => 'both', -expand => 1); + + my $msgSide = $config{orientation} || 'top'; + $msgSide = 'top' unless $msgSide eq 'bottom'; + + if ($msgSide eq 'top') { + $msgboxFrame = $chatFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'top', -fill => 'x'); + $dialogFrame = $chatFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'top', -fill => 'both', -expand => 1); + } + else { + $msgboxFrame = $chatFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'x'); + $dialogFrame = $chatFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'both', -expand => 1); + } + + ########################## + # Control Frame # + ########################## + + $controlFrame->Label ( + -image => $IMAGE{worlds}, + -border => 2, + -relief => 'raised', + )->pack (-side => 'left', -pady => 0, -padx => 0); + + $menu{loginlabel} = $controlFrame->Label ( + -text => "Name:", + -foreground => $config{windowfg}, + -background => $config{windowbg}, + -font => $FONT, + )->pack (-side => 'left', -padx => 2); + + $menu{logintext} = $controlFrame->Entry ( + -textvariable => \$user{nick}, + -foreground => $config{inputfg}, + -background => $config{inputbg}, + -disabledforeground => $config{windowfg}, + -disabledbackground => $config{windowbg}, + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->pack (-side => 'left', -padx => 2); + + $menu{loginbttn} = $controlFrame->Button ( + -text => 'Join Chat', + -foreground => $config{buttonfg}, + -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, + -activebackground => $config{buttonbg}, + -disabledforeground => $config{disabledfg}, + -state => 'disabled', + -font => $FONT, + -command => \&enterChat, + -highlightthickness => 0, + )->pack (-side => 'left', -padx => 2); + + $menu{constatus} = $controlFrame->Label ( + -text => 'Not connected to CyanChat.', + -foreground => $config{clientcolor}, + -background => $config{windowbg}, + -font => $FONT, + )->pack (-side => 'left', -padx => 2); + + ########################## + # Who Frame # + ########################## + + my $autobttnFrame = $btnFrame->Frame ( + -background => $config{windowbg}, + )->pack (-fill => 'x'); + + $menu{autobttn} = $autobttnFrame->Checkbutton ( + -text => 'Autoscroll', + -foreground => $config{buttonfg}, + -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, + -activebackground => $config{buttonbg}, + -font => $FONT, + -variable => \$config{autoscroll}, + -highlightthickness => 0, + )->pack (-side => 'left', -fill => 'x', -padx => 2, -pady => 1); + + my $tsFrame = $btnFrame->Frame ( + -background => $config{windowbg}, + )->pack (-fill => 'x'); + + $menu{timebttn} = $tsFrame->Checkbutton ( + -text => 'Time stamps', + -foreground => $config{buttonfg}, + -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, + -activebackground => $config{buttonbg}, + -font => $FONT, + -variable => \$config{timestamps}, + -highlightthickness => 0, + -command => sub { + my @opts = ( + -foreground => $config{background}, + -elide => 1, + ); + if ($config{timestamps} == 1) { + @opts = ( + -foreground => $config{servercolor}, + -elide => 0, + ); + } + $chat->tagConfigure ("timestamp", + -font => [ + @{$FONT}, + -size => 8, + ], + @opts, + ); + }, + )->pack (-side => 'left', -fill => 'x', -padx => 2, -pady => 1); + + $menu{privatebttn} = $btnFrame->Button ( + -text => 'Send Private', + -foreground => $config{buttonfg}, + -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, + -activebackground => $config{buttonbg}, + -disabledforeground => $config{disabledfg}, + -state => 'disabled', + -font => $FONT, + -command => \&sendPrivate, + -highlightthickness => 0, + )->pack (-fill => 'x', -padx => 2, -pady => 1); + + $menu{ignorebttn} = $btnFrame->Button ( + -text => 'Ignore', + -foreground => $config{buttonfg}, + -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, + -activebackground => $config{buttonbg}, + -disabledforeground => $config{disabledfg}, + -state => 'disabled', + -font => $FONT, + -command => \&ignoreUser, + -highlightthickness => 0, + )->pack (-fill => 'x', -padx => 2, -pady => 1); + + my $admnFrame = $whoFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'x'); + + $admnFrame->Label ( + -text => 'Cyan & Guests:', + -foreground => $config{windowfg}, + -background => $config{windowbg}, + -font => $FONT, + )->pack (-side => 'top', -anchor => 'w'); + + $adminlist = $admnFrame->Scrolled ('Listbox', + -foreground => $config{foreground}, + -background => $config{whobg}, + -scrollbars => 'osoe', + -height => 5, + -font => $FONT, + -selectforeground => '#000000', + -selectbackground => '#CCCCFF', + -highlightthickness => 0, + )->pack (-side => 'top', -fill => 'x'); + + my $wholistFrame = $whoFrame->Frame ( + -background => $config{windowbg}, + )->pack (-side => 'bottom', -fill => 'both', -expand => 1); + + $menu{wholabel} = $wholistFrame->Label ( + -text => 'Who is online:', + -foreground => $config{windowfg}, + -background => $config{windowbg}, + -font => $FONT, + )->pack (-side => 'top', -anchor => 'w'); + + $wholist = $wholistFrame->Scrolled ('Listbox', + -foreground => $config{foreground}, + -background => $config{whobg}, + -scrollbars => 'osoe', + -font => $FONT, + -selectforeground => '#000000', + -selectbackground => '#CCCCFF', + -highlightthickness => 0, + )->pack (-side => 'top', -fill => 'both', -expand => 1); + + # Bind the Who List for right-clicking and middle-clicking. + $wholist->bind ('', \&wholistRightClick); + $wholist->bind ('', \&wholistMiddleClick); + $wholist->bind ('', \&sendIM); + + # Bind the special Who List too. + $adminlist->bind ('', [\&wholistRightClick,"admin"]); + $adminlist->bind ('', \&wholistMiddleClick); + $adminlist->bind ('', \&adminlistSendIM); + + $menu{msgbox} = $msgboxFrame->Entry ( + -textvariable => \$user{msg}, + -foreground => $config{inputfg}, + -background => $config{inputbg}, + -disabledforeground => $config{windowfg}, + -disabledbackground => $config{windowbg}, + -state => 'disabled', + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->pack (-fill => 'x', -expand => 1); + + $chat = $dialogFrame->Scrolled ('ROText', + -foreground => $config{foreground}, + -background => $config{background}, + -scrollbars => 'ose', + -wrap => 'word', + -font => $FONT, + -highlightthickness => 0, + )->pack (-fill => 'both', -expand => 1); + + # Bind all the tags. + &bindChatTags(); + + # Add some introductory messages. + &sendLine (from => 'ChatClient', color => 'client', message => "Welcome to Perl CyanChat Client v. $VERSION!"); + + ########################## + # Key Bindings # + ########################## + + $mw->bind ('', \&bind_return); + $mw->bind ('', sub { + my $file = $mw->getSaveFile ( + -initialdir => '.', + -defaultextension => '.html', + -filetypes => [ + [ 'HTML Document', '*.html' ], + [ 'Text Document', '*.txt' ], + [ 'All Files', '*.*' ], + ], + ); + + return unless defined $file; + + if ($file =~ /\.txt$/i) { + # Save as plain text. + open (SAVE, ">$file"); + print SAVE $chat->get('1.0','end'); + close (SAVE); + } + else { + # Save as HTML. + &saveHTML ($file); + } + }); + $mw->bind ('', \&shutdown); + $mw->bind ('', sub { + $chat->findandreplacepopup (1); + }); + $mw->bind ('', sub { + $chat->selectAll; + }); + $mw->bind ('', sub { + &help(); + }); + $mw->bind ('', \&prefs); + $mw->bind ('', sub { exit(0); }); + + if ($config{autoconnect} == 1) { + &connect(); + } +} + +sub initConfig { + my $skip = shift || 'no'; + + # Specify the default settings in case there is no config file present. + %config = ( + chathost => 'cho.cyan.com', # ChatHost = the CyanChat server hostname + chatport => 1812, # ChatPort = the CyanChat server port + autoconnect => 0, # AutoConnect = automatically connect on startup + reconnect => 1, # ReConnect = automatically reconnect on disconnect + dialogfont => 'Arial', # DialogFont = the font for the CyanChat dialog widgets + reversechat => 1, # ReverseChat = new messages on top (default) + fontsize => 10, # FontSize = font size + autoscroll => 1, # AutoScroll = automatically scroll on new messages + nickname => '', # Nickname = a default value for nick + autojoin => 0, # AutoJoin = automatically join the chat (if length Nickname) + blockserver => 0, # BlockServer = ignore ChatServer's messages + ignoreback => 1, # IgnoreBack = perform mutual ignore + loudignore => 1, # LoudIgnore = show messages when people ignore you + sendignore => 1, # SendIgnore = send the ignore command when ignoring + autoact => 1, # AutoAct = *..* messages are /me equivalents + loudtypo => 1, # LoudTypo = show notifications about typo's + browser => $HTTPBROWSER, # Browser = console command to link URLs + orientation => 'top', # Orientation = input box's position + timestamps => 0, # TimeStamps = show timestamps on messages + imwindows => 1, # IMWindows = show "IM" style windows for private messages + stickyignore => 0, # StickyIgnore = remember who I ignored + notifyanimate => 1, # NotifyAnimate = animate the window titles for notifications + autologging => 0, # Autologging = automatically log chat dialog + mediaplayer => "play", # MediaPlayer = MPlayer program (not applicable to Windows) + playsounds => 1, # PlaySounds = global sound playing switch + playjoin => 1, # PlayJoin = play sound when user joins + playleave => 1, # PlayLeave = play sound when user leaves + playpublic => 0, # PlayPublic = play sound on public message + playprivate => 1, # PlayPrivate = play sound on private message + joinsound => "link.wav", # JoinSound = sound effect when user enters the room + leavesound => "link.wav", # LeaveSound = sound effect when user leaves the room + publicsound => "ding.wav", # PublicSound = sound effect to play on public message + privatesound => "message.wav", # PrivateSound = sound effect to play on private message + windowbg => '#000000', # WindowBG = the BG color for MainWindow + windowfg => '#CCCCCC', # WindowFG = the FG color for MainWindow + buttonbg => '#000000', # ButtonBG = the BG color for buttons + buttonfg => '#CCCCCC', # ButtonFG = the FG color for buttons + whobg => '#000000', # WhoBG = the BG color for Who List + background => '#000000', # Background = the BG color for CyanChat + foreground => '#CCCCCC', # Foreground = the FG color + inputbg => '#FFFFFF', # InputBG = text box input BG + inputfg => '#000000', # InputFG = text box input FG + disabledfg => '#999999', # DisabledFG = foreground for disabled buttons + linkcolor => '#0099FF', # LinkColor = hyperlink colors + usercolor => '#FFFFFF', # UserColor = white + echocolor => '#FFFFFF', # EchoColor = white + admincolor => '#00FFFF', # AdminColor = cyan + guestcolor => '#FF9900', # GuestColor = yellow + servercolor => '#00FF00', # ServerColor = lime + clientcolor => '#FF0000', # ClientColor = red + privatecolor => '#FF99FF', # PrivateColor = pink (magenta on black = ugly) + actioncolor => '#FFFF00', # ActionColor = orange + ); + + if ($skip ne 'skip' && -f "$homedir/config.txt") { + print "Reading configuration from $homedir/config.txt\n"; + open (CFG, "$homedir/config.txt"); + my @cfg = ; + close (CFG); + chomp @cfg; + + foreach my $line (@cfg) { + next unless defined $line; + next if $line eq ''; + next unless length $line > 0; + + my ($label,$data) = split(/\s+/, $line, 2); + $label = lc($label); + + $config{$label} = $data; + } + } + + $FONT = [ + -family => $config{dialogfont}, + -size => $config{fontsize}, + ]; + + if ($skip ne 'cancel') { + $user{nick} = $config{nickname}; + } + + $HTTPBROWSER = $config{browser}; + + # Reload our saved ignore lists? + if (-f "$homedir/ignore.txt") { + print "Reading saved ignore list from $homedir/ignore.txt\n"; + open (READ, "$homedir/ignore.txt"); + my @read = ; + close (READ); + chomp @read; + + foreach my $line (@read) { + print "$line\n"; + $ignore{$line} = 1; + } + } +} + +############################################ +## Main Methods ## +############################################ + +sub bindChatTags { + foreach (qw(user admin guest server client private action echo)) { + my $var = $_ . "color"; + $chat->tagConfigure ($_, -foreground => $config{$var}); + } + + my @opts = ( + -foreground => $config{background}, + -elide => 1, + ); + if ($config{timestamps} == 1) { + @opts = ( + -foreground => $config{servercolor}, + -elide => 0, + ); + } + $chat->tagConfigure ("timestamp", + -font => [ + @{$FONT}, + -size => 8, + ], + @opts, + ); + + $chat->configure (-foreground => $config{foreground}, -background => $config{background}); + + # (Re)color the window. + $controlFrame->configure (-background => $config{windowbg}); + $mainFrame->configure (-background => $config{windowbg}); + $rightFrame->configure (-background => $config{windowbg}); + $btnFrame->configure (-background => $config{windowbg}); + $whoFrame->configure (-background => $config{windowbg}); + $chatFrame->configure (-background => $config{windowbg}); + + $menu{loginlabel}->configure (-foreground => $config{windowfg}, -background => $config{windowbg}); + $menu{logintext}->configure (-disabledforeground => $config{windowfg}, -disabledbackground => $config{windowbg}, + -foreground => $config{inputfg}, -background => $config{inputbg}); + $menu{loginbttn}->configure (-foreground => $config{buttonfg}, -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, -activebackground => $config{buttonbg}, -disabledforeground => $config{disabledfg}); + $menu{constatus}->configure (-background => $config{windowbg}); + $menu{privatebttn}->configure (-foreground => $config{buttonfg}, -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, -activebackground => $config{buttonbg}, -disabledforeground => $config{disabledfg}); + $menu{ignorebttn}->configure (-foreground => $config{buttonfg}, -background => $config{buttonbg}, + -activeforeground => $config{buttonfg}, -activebackground => $config{buttonbg}, -disabledforeground => $config{disabledfg}); + $menu{wholabel}->configure (-foreground => $config{windowfg}, -background => $config{windowbg}); + $wholist->configure (-foreground => $config{foreground}, -background => $config{whobg}); + $menu{msgbox}->configure (-disabledforeground => $config{windowfg}, -disabledbackground => $config{windowbg}, + -foreground => $config{inputfg}, -background => $config{inputbg}); + + # Refresh the Who List. + &updateWhoList; + + # Update the connection status label. + if ($connected) { + $menu{constatus}->configure (-foreground => $config{servercolor}); + } + else { + $menu{constatus}->configure (-foreground => $config{clientcolor}); + } +} + +sub sendMsgLine { + my ($where,$str,$deftag) = @_; # where = '0.0' or 'end' + $deftag = '' unless defined $deftag; + + #print "sendMsgLine ($where,$str)\n"; + + # This is a universal subroutine for sending the "message" part of a user's message + # to a chat or private message window. This allows the hyperlinking function to be + # easy and universal. + + # Isolate the hyperlinks. + $str =~ s~(\s*)((http|https|ftp)://[^\s]+)(\s*)~$1$2$4~ig; + + # Split the message at hyperlinks. + my @parts = split(/insert ($where,"\n"); + @parts = reverse(@parts); + } + + # Go through each one. + foreach my $part (@parts) { + #print "part: $part\n"; + + # If this part is to a hyperlink... + if ($part =~ /^httphyperlink>/) { + # Cut off the PCCC Hyperlink tag. + $part =~ s/^httphyperlink>//i; + + #print ":: Found a hyperlink: $part\n"; + + # Create a unique hyperlink tag. + my $tag = "hyperlink" . $hyperlink++; + $chat->tagConfigure ($tag, -underline => 1, -foreground => $config{linkcolor}); + + # Bind this tag to an anonymous function. + $chat->tagBind ($tag, "", [ + sub { + my $link = $_[1]; + #print "link clicked: $link\n"; + push (@HYPERLINKLIST, $link); + }, + $part, + ]); + $chat->tagBind ($tag,"", sub { + $chat->configure (-cursor => 'hand2'); + }); + $chat->tagBind ($tag,"", sub { + $chat->configure (-cursor => 'xterm'); + }); + + # Insert this. + $chat->insert ($where,$part,$tag); + } + else { + $part =~ s/^httpendhyperlink>//i; + $chat->insert ($where,$part,$deftag); + } + } + + if ($where eq 'end') { + $chat->insert ($where,"\n"); + } +} + +sub sendLine { + my (%data) = @_; + + if ($data{from} eq $user{nick}) { + $data{color} = 'echo'; + } + + my $stamp = ×tamp; + + push (@xhtml, "
" + . "$stamp [$data{from}] " + . &htmlEscape($data{message}) . "
"); + + #print "sendLine (" . each(%data) . ")\n"; + + if ($config{reversechat} == 1) { + &sendMsgLine ('0.0',$data{message}); + $chat->insert ('0.0', "[$data{from}] ",$data{color}); + $chat->insert ('0.0', "$stamp ","timestamp"); + if ($config{autoscroll} == 1) { + $chat->see ('0.0'); + } + } + else { + $chat->insert ('end', "$stamp ","timestamp"); + $chat->insert ('end', "[$data{from}] ",$data{color}); + &sendMsgLine ('end',$data{message}); + if ($config{autoscroll} == 1) { + $chat->see ('end'); + } + } + + if ($config{notifyanimate} == 1 && $winanim->{__mainwindow__}->{focused} == 0) { + $winanim->{__mainwindow__}->{animating} = 1; + } + &doAutolog(); +} +sub sendBlankLine { + push (@xhtml, "
 
"); + + if ($config{reversechat} == 1) { + $chat->insert ('0.0', "\n"); + if ($config{autoscroll} == 1) { + $chat->see ('0.0'); + } + } + else { + $chat->insert ('end',"\n"); + if ($config{autoscroll} == 1) { + $chat->see ('end'); + } + } + + if ($config{notifyanimate} == 1 && $winanim->{__mainwindow__}->{focused} == 0) { + $winanim->{__mainwindow__}->{animating} = 1; + } + &doAutolog(); +} +sub sendMoveLine { + my (%data) = @_; + + if ($data{from} eq $user{nick}) { + $data{color} = 'echo'; + } + + my $escape = &htmlEscape($data{message}); + my $stamp = ×tamp; + + push (@xhtml, "
$stamp " + . "$data{prefix}" + . "[$data{from}] $escape" + . "$data{suffix}
"); + + if ($config{reversechat} == 1) { + $chat->insert ('0.0', "$data{suffix}\n",'server'); + $chat->insert ('0.0', "$data{message}"); + $chat->insert ('0.0', "[$data{from}] ",$data{color}); + $chat->insert ('0.0', "$data{prefix}",'server'); + $chat->insert ('0.0', "$stamp ",'timestamp'); + if ($config{autoscroll} == 1) { + $chat->see ('0.0'); + } + } + else { + $chat->insert ('end', "$stamp ",'timestamp'); + $chat->insert ('end', "$data{prefix}",'server'); + $chat->insert ('end', "[$data{from}] ",$data{color}); + $chat->insert ('end', "$data{message}"); + $chat->insert ('end', "$data{suffix}\n",'server'); + if ($config{autoscroll} == 1) { + $chat->see ('end'); + } + } + + if ($config{notifyanimate} == 1 && $winanim->{__mainwindow__}->{focused} == 0) { + $winanim->{__mainwindow__}->{animating} = 1; + } + &doAutolog(); +} +sub sendActionLine { + my (%data) = @_; + + my ($typo) = (exists $data{typo} && $data{typo} eq "true") ? "true" : "false"; + + if ($data{from} eq $user{nick}) { + $data{color} = 'echo'; + } + + my $stamp = ×tamp; + + push (@xhtml, "
$stamp " + . "[$data{from}] " + . "" + . &htmlEscape($data{message}) . "
"); + + if ($config{reversechat} == 1) { + if ($typo eq "true") { + &sendMsgLine ('0.0',$data{message},'action'); + $chat->insert ('0.0', "[$data{from}] ",$data{color}); + } + else { + &sendMsgLine ('0.0',"$data{message} **",'action'); + $chat->insert ('0.0', "$data{from} ",$data{color}); + $chat->insert ('0.0', "** ",'action'); + } + $chat->insert ('0.0', "$stamp ",'timestamp'); + if ($config{autoscroll} == 1) { + $chat->see ('0.0'); + } + } + else { + $chat->insert ('end', "$stamp ",'timestamp'); + if ($typo eq "true") { + $chat->insert ('end', "[$data{from}] ",$data{color}); + &sendMsgLine ('end',$data{message},'action'); + } + else { + $chat->insert ('end', "** ",'action'); + $chat->insert ('end', "$data{from} ",$data{color}); + &sendMsgLine ('end',"$data{message} **",'action'); + } + if ($config{autoscroll} == 1) { + $chat->see ('end'); + } + } + + if ($config{notifyanimate} == 1 && $winanim->{__mainwindow__}->{focused} == 0) { + $winanim->{__mainwindow__}->{animating} = 1; + } + &doAutolog(); +} +sub sendPrivLine { + my (%data) = @_; + + my $stamp = ×tamp; + + if ($data{from} eq $user{nick}) { + $data{color} = 'echo'; + } + + my $popupImWindow = 0; + if ($config{imwindows} == 1) { + $popupImWindow = 1; + } + elsif (exists $data{popup} && $data{popup} == 1) { + $popupImWindow = 1; + } + + if ($popupImWindow == 1 && not exists $windows{$data{from}}) { + # Set us up for window animating. + $winanim->{$data{from}} = { + title => "$data{from} | CyanChat", + focused => -1, + animating => 0, + phase => 0, + proceed => 0, + }; + + $windows{$data{from}} = $mw->Toplevel ( + -title => "$data{from} | CyanChat", + ); + $windows{$data{from}}->geometry ('320x240'); + $windows{$data{from}}->Icon (-image => $IMAGE{balloon}); + $windows{$data{from}}->bind ('', [ sub { + my $id = $_[1]; + delete $winanim->{$id}; + delete $windows{$id}; + }, $data{from}]); + $windows{$data{from}}->bind ('', [ sub { + $windows{$data{from}}->configure (-title => "$_[1] | CyanChat"); + $pfocus{$_[1]} = 1; + $winanim->{$_[1]}->{focused} = 1; + &animReset($_[1]); + }, $data{from}]); + $windows{$data{from}}->bind ('', [ sub { + $pfocus{$_[1]} = 0; + $winanim->{$_[1]}->{focused} = 0; + }, $data{from}]); + + my $Frame = $windows{$data{from}}->Frame ( + -background => $config{background}, + )->pack (-side => 'top', -fill => 'both', -expand => 1); + + my $inputFrame = $Frame->Frame ( + -background => $config{background}, + )->pack (-side => 'top', -fill => 'x'); + my $dlgFrame = $Frame->Frame ( + -background => $config{background}, + )->pack (-side => 'top', -fill => 'both', -expand => 1); + + $inputFrame->Entry ( + -textvariable => \$pmsg{$data{from}}, + -foreground => $config{inputfg}, + -background => $config{inputbg}, + -font => $FONT, + -highlightthickness => 0, + )->pack (-fill => 'x', -expand => 1)->focusForce; + + $private{$data{from}} = $dlgFrame->Scrolled ('ROText', + -foreground => $config{foreground}, + -background => $config{background}, + -scrollbars => 'ose', + -wrap => 'word', + -font => $FONT, + -highlightthickness => 0, + )->pack (-fill => 'both', -expand => 1); + + $private{$data{from}}->tagConfigure ('user', -foreground => $config{usercolor}); + $private{$data{from}}->tagConfigure ('private', -foreground => $config{privatecolor}); + + if (defined $data{default}) { + $private{$data{from}}->insert ('end', "[$user{nick}] ",'user'); + $private{$data{from}}->insert ('end', "$data{default}\n"); + $private{$data{from}}->see ('end'); + } + + $windows{$data{from}}->focusForce; + $pfocus{$data{from}} = 1; + + $windows{$data{from}}->bind ('', [ sub { + my $user = $_[1]; + + if (length $pmsg{$user}) { + $private{$data{from}}->insert ('end', "[$user{nick}] ",'user'); + $private{$data{from}}->insert ('end', "$pmsg{$user}\n"); + $private{$data{from}}->see ('end'); + &sendLine (from => 'ChatClient', color => 'client', message => "Private message sent to: [$user] $pmsg{$user}"); + $netcc->sendPrivate ($user,$pmsg{$user}); + $pmsg{$user} = ''; + } + }, $data{from} ]); + } + + return unless length $data{message}; + + if (exists $windows{$data{from}}) { + $private{$data{from}}->insert ('end', "[$data{from}] ",'private'); + $private{$data{from}}->insert ('end', "$data{message}\n"); + + if ($pfocus{$data{from}} == 0) { + $windows{$data{from}}->configure (-title => ">>> $data{from} | CyanChat <<<"); + } + + $private{$data{from}}->see ('end'); + + if ($config{notifyanimate} == 1 && $winanim->{$data{from}}->{focused} == 0) { + $winanim->{$data{from}}->{animating} = 1; + } + } + + push (@xhtml, "
$stamp " + . "Private message from " + . "[$data{from}] " + . &htmlEscape($data{message}) . "
"); + + if ($config{reversechat} == 1) { + &sendMsgLine ('0.0',$data{message}); + $chat->insert ('0.0', "[$data{from}] ",$data{color}); + $chat->insert ('0.0', "Private message from ",'private'); + $chat->insert ('0.0', "$stamp ",'timestamp'); + if ($config{autoscroll} == 1) { + $chat->see ('0.0'); + } + } + else { + $chat->insert ('end', "$stamp ",'timestamp'); + $chat->insert ('end', "Private message from ",'private'); + $chat->insert ('end', "[$data{from}] ",$data{color}); + &sendMsgLine ('end',$data{message}); + if ($config{autoscroll} == 1) { + $chat->see ('end'); + } + } + + if ($config{notifyanimate} == 1 && $winanim->{__mainwindow__}->{focused} == 0) { + $winanim->{__mainwindow__}->{animating} = 1; + } + &doAutolog(); +} + +sub connect { + # Create a new Net::CyanChat object. + $netcc = new Net::CyanChat ( + host => $config{chathost}, + port => $config{chatport}, + proto => 1, + debug => 1, + ); + + # Set handlers. + $netcc->setHandler (Connected => \&on_connected); + $netcc->setHandler (Disconnected => \&on_disconnected); + $netcc->setHandler (Welcome => \&on_welcome); + $netcc->setHandler (Message => \&on_message); + $netcc->setHandler (Private => \&on_private); + $netcc->setHandler (Chat_Buddy_In => \&on_enter); + $netcc->setHandler (Chat_Buddy_Out => \&on_exit); + #$netcc->setHandler (Chat_Buddy_Here => \&on_here); + $netcc->setHandler (WhoList => \&on_wholist); + $netcc->setHandler (Name_Accepted => \&on_name_accepted); + $netcc->setHandler (Ignored => \&on_ignored); + $netcc->setHandler (Packet => \&on_packet); + $netcc->setHandler (Error => \&on_error); + + &sendBlankLine(); + &sendLine (from => 'ChatClient', color => 'client', message => "Connecting to CyanChat..."); + + $menu{constatus}->configure ( + -text => 'Connecting...', + -foreground => $config{clientcolor}, + ); + + # Connect. + $connected = 1; + $netcc->connect(); +} + +sub disconnect { + if ($loggedin) { + &exitChat; + } + + $netcc->{sock}->close(); + $netcc = undef; + + $connected = 0; + $menu{constatus}->configure ( + -text => 'Not connected.', + -foreground => $config{clientcolor}, + ); + $menu{connect}->configure (-state => 'normal'); + $menu{disconnect}->configure (-state => 'disabled'); + $menu{details}->configure (-state => 'disabled'); + $menu{rawmenu}->configure (-state => 'disabled'); + $menu{loginbttn}->configure (-state => 'disabled'); + $menu{privatebttn}->configure (-state => 'disabled'); + $menu{ignorebttn}->configure (-state => 'disabled'); + $menu{logintext}->focusForce; + + # Clear the who list. + $wholist->delete (0,'end'); + %online = (); + %ignore = (); +} + +sub enterChat { + my $nick = $user{nick}; + + if (length $nick) { + if (length $nick > 20 || $nick =~ /\|/) { + &sendLine (from => 'ChatClient', color => 'client', message => "Your nickname must be less than 20 characters " + . "and cannot contain a pipe symbol (\"|\")"); + } + else { + # It should be good. + $netcc->login ($nick); + } + } + else { + &sendLine (from => 'ChatClient', color => 'client', message => "Please enter a nickname before joining chat."); + } +} + +sub exitChat { + $netcc->logout; + + $loggedin = 0; + + $menu{loginbttn}->configure ( + -text => 'Join Chat', + -command => \&enterChat, + ); + $menu{logintext}->configure ( + -state => 'normal', + ); + $menu{msgbox}->configure ( + -state => 'disabled', + ); + $menu{logintext}->focusForce; +} + +sub sendMessage { + my $msg = $user{msg} || ''; + + # Filter line breaks from the message (they might've accidentally been pasted in). + $msg =~ s/\x0a//g; + $msg =~ s/\x0d//g; + + if (length $msg > 0 && $loggedin == 1) { + # Run commands. + if ($msg =~ /^\/(?:whisper|w|msg) (.+?)$/i) { + my ($to,$what) = split(/\s+/, $1, 2); + + if (length $to && length $what) { + if (not exists $private{$to}) { + &sendPrivLine (from => $to, color => 'server', message => '', default => $what); + } + &sendLine (from => 'ChatClient', color => 'client', message => "Private message sent to: [$to] $what"); + $netcc->sendPrivate ($to,$what); + } + else { + &sendLine (from => 'ChatClient', color => 'client', message => "Usage: /whisper "); + } + + $user{msg} = ''; + } + else { + $netcc->sendMessage ($msg); + $user{msg} = ''; + } + } +} + +sub sendPrivate { + # Get the selected user. + my $index = ($wholist->curselection)[0]; + + if (length $index) { + my $user = $wholist->get ($index); + my $msg = $user{msg} || ''; + + if (length $msg > 0 && length $user > 0 && $loggedin == 1) { + if (not exists $private{$user}) { + &sendPrivLine (from => $user, color => 'server', message => '', default => $user{msg}); + } + + &sendLine (from => 'ChatClient', color => 'client', message => "Private message sent to: [$user] $msg"); + $netcc->sendPrivate ($user,$msg); + $user{msg} = ''; + } + else { + &sendLine (from => 'ChatClient', color => 'client', message => "Select a user from the Who List and write a message."); + } + } + else { + &sendLine (from => 'ChatClient', color => 'client', message => "Select a user from the Who List and write a message."); + } +} + +sub sendIM { + # Get the selected user. + my $name = $_[1] || undef; + + my $user = undef; + + if (not defined $name) { + my $index = ($wholist->curselection)[0]; + $user = $wholist->get ($index); + } + else { + $user = $name; + } + + my $msg = $user{msg} || ''; + + if (exists $windows{$user}) { + $windows{$user}->focusForce; + } + else { + &sendPrivLine (from => $user, color => 'server', message => '', popup => 1); + } +} + +sub adminlistSendIM { + # Get the selected user. + my $index = ($adminlist->curselection)[0]; + + if (length $index) { + my $user = $adminlist->get ($index); + my $msg = $user{msg} || ''; + + if (exists $windows{$user}) { + $windows{$user}->focusForce; + } + else { + &sendPrivLine (from => $user, color => 'server', message => '', popup => 1); + } + } +} + +sub ignoreUser { + my $name = $_[1] || undef; + + #print "ignoreUser(@_)\n"; + + my $user = undef; + if (not defined $name) { + #print "name not defined\n"; + my $index = ($wholist->curselection)[0]; + + if (length $index) { + $user = $wholist->get ($index); + } + } + else { + $user = $name; + } + + return unless defined $user; + + if (exists $ignore{$user}) { + if ($config{sendignore} == 1) { + $netcc->unignore ($user); + } + delete $ignore{$user}; + &sendLine (from => 'ChatClient', color => 'client', message => "No longer ignoring messages from $user."); + } + else { + if ($config{sendignore} == 1) { + $netcc->ignore ($user); + } + $ignore{$user} = 1; + &sendLine (from => 'ChatClient', color => 'client', message => "Now ingoring messages from $user."); + } +} + +sub wholistRightClick { + my $listbox = shift; + my $admin = shift || ''; + + # Get the cursor position. + my $cursor = $Tk::event->y; + + # Find out what name we're over. + my $name = $listbox->get ($listbox->nearest ($cursor)); + + # Select this user. + $listbox->selectionClear (0,'end'); + $listbox->selectionSet ($listbox->nearest($cursor)); + + # Return if something went wrong. + return unless length $name; + + # Get their address. + my ($level,$addr) = split(/\;/, $online{$name}, 2); + + &sendLine ( + from => 'ChatClient', + color => 'client', + message => "$name is chatting from the address $addr", + ); +} + +sub wholistMiddleClick { + my $listbox = shift; + + # Get the cursor position. + my $cursor = $Tk::event->y; + + # Find out what name we're over. + my $name = $listbox->get ($listbox->nearest ($cursor)); + + # Select this user. + $listbox->selectionClear (0,'end'); + $listbox->selectionSet ($listbox->nearest($cursor)); + + # Return if something went wrong. + return unless length $name; + + # Add their name to our message. + $user{msg} .= $name; +} + +sub getColor { + my $code = shift; + + if ($code == 0) { + return 'user'; + } + elsif ($code == 1) { + return 'admin'; + } + elsif ($code == 2) { + return 'server'; + } + elsif ($code == 4) { + return 'guest'; + } + + return 'client'; +} + +sub savePrefs { + # Save preferences in a logical order. + my @order = ( + 'ChatHost', + 'ChatPort', + 'AutoConnect', + 'ReConnect', + 'DialogFont', + 'ReverseChat', + 'FontSize', + 'AutoScroll', + 'Nickname', + 'AutoJoin', + 'BlockServer', + 'IgnoreBack', + 'LoudIgnore', + 'SendIgnore', + 'AutoAct', + 'LoudTypo', + 'Browser', + 'Orientation', + 'TimeStamps', + 'IMWindows', + 'StickyIgnore', + 'NotifyAnimate', + 'Autologging', + 'MediaPlayer', + 'PlaySounds', + 'PlayJoin', + 'PlayLeave', + 'PlayPublic', + 'PlayPrivate', + 'JoinSound', + 'LeaveSound', + 'PublicSound', + 'PrivateSound', + 'WindowBG', + 'WindowFG', + 'ButtonBG', + 'ButtonFG', + 'WhoBG', + 'Background', + 'Foreground', + 'InputBG', + 'InputFG', + 'DisabledFG', + 'LinkColor', + 'UserColor', + 'EchoColor', + 'AdminColor', + 'GuestColor', + 'ServerColor', + 'ClientColor', + 'PrivateColor', + 'ActionColor', + ); + + $HTTPBROWSER = $config{browser}; + + my @lines = (); + #print "lines = @lines\n"; + foreach (@order) { + my $var = lc($_); + + $_ .= " " until length $_ == 15; + + push (@lines, $_ . $config{$var}); + } + + open (SAVE, ">$homedir/config.txt"); + print SAVE join ("\n",@lines); + close (SAVE); +} + +sub shutdown { + $menu{forcequit}->configure (-state => 'normal'); + + # Save our ignore list. + if ($config{stickyignore} == 1) { + #print "Save ignore list:\n"; + print Dumper(%ignore); + my @save = (); + foreach my $key (keys %ignore) { + push (@save,"$key"); + } + #print join ("\n",@save); + + open (IGNORE, ">$homedir/ignore.txt"); + print IGNORE join ("\n",@save); + close (IGNORE); + } + else { + # We no longer want to save ignores, so delete the file. + if (-f "$homedir/ignore.txt") { + #print "Delete the ignore list\n"; + unlink ("$homedir/ignore.txt"); + } + } + + # If we're connected... + if ($connected) { + my $dialog = $mw->Dialog ( + -title => 'Exit PCCC?', + -text => "You are currently connected to CyanChat. Disconnect and exit?", + -buttons => [ 'Yes', 'No' ], + -default_button => 'Yes', + ); + $dialog->Icon (-image => $IMAGE{worlds}); + + my $choice = $dialog->Show; + if ($choice =~ /yes/i) { + &disconnect(); + } + else { + return; + } + } + + $mw->destroy; + + # Send the link thread a signal to start wrapping things up. + push (@HYPERLINKLIST, "+shutdown"); + push (@PLAYSOUNDS, "+shutdown"); + + # Join the child threads. + $linkthread->join; + $mediathread->join; + + exit(0); +} + +sub loop { + $| = 1; + while (1) { + select (undef,undef,undef,0.001); + $mw->update; + + if ($connected) { + $netcc->do_one_loop; + } + + # Animate all windows. + foreach my $winName (keys %{$winanim}) { + if ($winanim->{$winName}->{animating} == 1) { + &animStep ($winName); + } + } + } +} + +sub animReset { + my $name = shift; + + if ($name eq '__mainwindow__') { + #print "Reset animation for MW\n"; + $mw->configure (-title => $winanim->{__mainwindow__}->{title}); + $winanim->{__mainwindow__} = { + title => $winanim->{__mainwindow__}->{title}, + focused => $winanim->{__mainwindow__}->{focused}, + phase => 0, + animating => 0, + proceed => 0, + }; + } + else { + #print "Reset animation for $name\n"; + $windows{$name}->configure (-title => $winanim->{$name}->{title}); + $winanim->{$name} = { + title => $winanim->{$name}->{title}, + focused => $winanim->{$name}->{focused}, + phase => 0, + animating => 0, + proceed => 0, + }; + } +} + +sub animStep { + my $name = shift; + + if ($name eq '__mainwindow__') { + if ($winanim->{$name}->{focused} == 1) { + &animReset('__mainwindow__'); + return; + } + + if ($winanim->{__mainwindow__}->{proceed} <= 0) { + # Up the phase. + $winanim->{__mainwindow__}->{phase}++; + if ($winanim->{__mainwindow__}->{phase} >= scalar @{$notification}) { + $winanim->{__mainwindow__}->{phase} = 0; + } + + my $suffix = $notification->[ $winanim->{__mainwindow__}->{phase} ]; + my ($left,$right) = @{$suffix}; + + #print "step: $left$winanim->{__mainwindow__}->{title}$right\n"; + + $mw->configure (-title => $left . $winanim->{__mainwindow__}->{title} . $right); + $winanim->{__mainwindow__}->{proceed} = 200; + } + $winanim->{__mainwindow__}->{proceed}--; + } + else { + if ($winanim->{$name}->{focused} == 1) { + &animReset($name); + return; + } + + if ($winanim->{$name}->{proceed} <= 0) { + # Up the phase. + $winanim->{$name}->{phase}++; + if ($winanim->{$name}->{phase} >= scalar @{$notification}) { + $winanim->{$name}->{phase} = 0; + } + + my $suffix = $notification->[ $winanim->{$name}->{phase} ]; + my ($left,$right) = @{$suffix}; + + #print "step: $left$winanim->{$name}->{title}$right\n"; + + $windows{$name}->configure (-title => $left . $winanim->{$name}->{title} . $right); + $winanim->{$name}->{proceed} = 200; + } + $winanim->{$name}->{proceed}--; + } +} + +############################################ +## Keyboard Bindings and Events ## +############################################ + +sub bind_return { + if ($connected) { + if (not $loggedin) { + &enterChat; + } + else { + &sendMessage; + } + } +} + +############################################ +## Handlers ## +############################################ + +sub on_connected { + my $cc = shift; + + $menu{constatus}->configure ( + -text => 'Connected to CyanChat.', + -foreground => $config{servercolor}, + ); + $menu{connect}->configure (-state => 'disabled'); + $menu{disconnect}->configure (-state => 'normal'); + $menu{details}->configure (-state => 'normal'); + $menu{rawmenu}->configure (-state => 'normal'); + $menu{loginbttn}->configure (-state => 'normal'); + $menu{privatebttn}->configure (-state => 'normal'); + $menu{ignorebttn}->configure (-state => 'normal'); + $menu{logintext}->focusForce; + + &sendLine (from => 'ChatClient', color => 'client', message => "Connection established!"); + + # If auto-login... + if ($config{autojoin} == 1) { + if (length $config{nickname}) { + $cc->login ($config{nickname}); + } + } +} + +sub on_disconnected { + my $cc = shift; + + $connected = 0; + $menu{constatus}->configure ( + -text => 'Not connected.', + -foreground => $config{clientcolor}, + ); + $menu{connect}->configure (-state => 'normal'); + $menu{disconnect}->configure (-state => 'disabled'); + $menu{details}->configure (-state => 'disabled'); + $menu{rawmenu}->configure (-state => 'disabled'); + $menu{loginbttn}->configure (-state => 'disabled'); + $menu{privatebttn}->configure (-state => 'disabled'); + $menu{ignorebttn}->configure (-state => 'disabled'); + + $loggedin = 0; + $menu{loginbttn}->configure ( + -text => 'Join Chat', + -command => \&enterChat, + ); + $menu{logintext}->configure ( + -state => 'normal', + ); + $menu{msgbox}->configure ( + -state => 'disabled', + ); + $menu{logintext}->focusForce; + + &sendLine (from => 'ChatClient', color => 'client', message => "You have been disconnected from the server."); + + # Clear the Who List. + $wholist->delete (0,'end'); + + if ($config{reconnect}) { + &connect(); + } +} + +sub on_packet { + my ($cc,$source,$packet) = @_; + + if ($source eq "incoming") { + $dbgtext->insert ('end',"$packet\n","server"); + } + else { + $dbgtext->insert ('end',"$packet\n","client"); + } +} + +sub on_welcome { + my ($cc,$msg) = @_; + + $msg =~ s/^\d//g; + $msg =~ s/\r//g; + + &sendLine (from => 'ChatServer', color => 'server', message => $msg); +} + +sub on_message { + my ($cc,$nick,$level,$addr,$msg) = @_; + + $msg =~ s/\r//g; + + if ($level == 2) { + # Ignoring server messages? + if ($config{blockserver} == 1) { + return; + } + } + + if (not exists $ignore{$nick}) { + &playSound ("public"); + if ($msg =~ /^\/me (.+?)$/i) { + &sendActionLine (from => $nick, color => &getColor($level), message => $1); + } + elsif ($config{autoact} == 1 && $msg =~ /^\*(.+?)\*$/i) { + &sendActionLine (from => $nick, color => &getColor($level), message => $1); + } + elsif ($config{loudtypo} == 1 && $msg =~ /^\*([^\*]+?)$/i) { + &sendActionLine (from => $nick, color => &getColor($level), message => $msg, typo => 'true'); + } + else { + &sendLine (time => time(), from => $nick, color => &getColor($level), message => $msg); + } + } +} + +sub on_private { + my ($cc,$nick,$level,$addr,$msg) = @_; + + $msg =~ s/\r//g; + + if ($level == 2) { + # Ignoring server messages? + if ($config{blockserver} == 1) { + return; + } + } + + if (not exists $ignore{$nick}) { + &playSound ("private"); + &sendPrivLine (from => $nick, color => &getColor($level), message => $msg); + } +} + +sub on_enter { + my ($cc,$nick,$level,$addr,$msg) = @_; + + &playSound ("join"); + + $msg =~ s/\r//g; + &sendMoveLine (from => $nick, color => &getColor($level), message => $msg, prefix => '\\\\\\\\\\', suffix => '/////'); + + $online{$nick} = join (";",$level,$addr); + &updateWhoList; + + # Play a SFX. + #push (@PLAYSOUNDS, "email.wav"); +} + +sub on_exit { + my ($cc,$nick,$level,$addr,$msg) = @_; + + &playSound ("leave"); + + $msg =~ s/\r//g; + &sendMoveLine (from => $nick, color => &getColor($level), message => $msg, prefix => '/////', suffix => '\\\\\\\\\\'); + + delete $online{$nick}; + &updateWhoList; +} + +sub on_here { + my ($cc,$nick,$level,$addr) = @_; + + my $user = $nick; + + if (exists $online{$user}) { + # Ignore. + } + else { + $online{$nick} = join (";",$level,$addr); + } + + &updateWhoList; +} + +sub on_wholist { + my ($cc,@users) = @_; + + #print "wholist: @users\n"; + + my %comp = (); + foreach my $name (@users) { + my ($nick,$addr) = split(/\,/, $name, 2); + my $user = $nick; + my ($level) = $user =~ /^(\d)/; + $user =~ s/^\d//; + + #print "check newuser $user\n"; + + if (exists $online{$user}) { + # They're already here. + #print "\tuser already here\n"; + } + else { + # They're new. + #print "\tthis is a new user!\n"; + } + + $online{$user} = join (";",$level,$addr); + + $comp{$user} = 1; + } + + # Look for missing users. + foreach my $nln (keys %online) { + #print "marked online user: $nln\n"; + if (not exists $comp{$nln}) { + #print "not exists in comp\n"; + # Delete them. + delete $online{$nln}; + } + } + + &updateWhoList(); +} + +sub updateWhoList { + $wholist->delete (0,'end'); + $adminlist->delete (0,'end'); + + foreach my $user (keys %online) { + my ($level,$addr) = split(/\;/, $online{$user}, 2); + my $color = &getColor($level); + my $type = join ('',$color,'color'); + + #print "Update wholists ($user; $level; $addr; $color; $type)\n"; + + if ($color eq 'admin' || $color eq 'guest') { + $adminlist->insert ('end',$user); + $adminlist->itemconfigure ('end', -foreground => $config{$type}); + } + else { + $wholist->insert ('end',$user); + $wholist->itemconfigure ('end', -foreground => $config{$type}); + } + } +} + +sub on_name_accepted { + my ($cc) = @_; + + $loggedin = 1; + + # Our name was accepted! + $menu{loginbttn}->configure ( + -text => 'Exit Chat', + -command => \&exitChat, + ); + $menu{logintext}->configure ( + -state => 'disabled', + ); + $menu{msgbox}->configure ( + -state => 'normal', + ); + + $menu{msgbox}->focusForce; +} + +sub on_ignored { + my ($cc,$ignore,$user) = @_; + + # Showing ignore notifications? + if ($config{loudignore} == 1) { + &sendLine (time => time(), from => 'ChatClient', color => 'client', message => "$user has ignored you."); + } + + # Doing mutual ignores? + if ($config{ignoreback} == 1) { + $cc->ignore ($user); + } +} + +sub on_error { + my ($cc,$code,$string) = @_; + + &sendLine (from => 'ChatServer', color => 'server', message => $string); +} + +sub htmlEscape { + my $str = shift; + + $str =~ s//>/g; + $str =~ s/\"/"/g; + $str =~ s/\'/'/g; + + return $str; +} + +sub timestamp { + # Generate the time stamp. + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); + $hour = "0" . $hour until length $hour == 2; + $min = "0" . $min until length $min == 2; + + return "$hour:$min"; +} + +sub doAutolog { + # Is autologging enabled? + if ($config{autologging} == 1) { + # Make log directories, if necessary. + my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime(time()); + $mon++; + $mon = "0" . $mon until length $mon == 2; + $day = "0" . $day until length $day == 2; + $year += 1900; + my $dir = join ("-",$year,$mon,$day); + + if (!-d "$homedir/logs") { + print "mkdir $homedir/logs\n"; + mkdir ("$homedir/logs") or warn "can't mkdir $homedir/logs: $!"; + } + if (!-d "$homedir/logs/$dir") { + print "mkdir $homedir/logs/$dir\n"; + mkdir ("$homedir/logs/$dir") or warn "can't mkdir $homedir/logs/$dir: $!"; + } + + # Does our filename already exist? + my $file = "error.html"; + if ($autologid == 0) { + my $i = 1; + $file = join ("",$year,$mon,$day) . "-$i.html"; + while (-f "$homedir/logs/$dir/$file") { + $i++; + $file = join ("",$year,$mon,$day) . "-$i.html"; + } + $autologid = $i; + } + else { + $file = join ("",$year,$mon,$day) . "-$autologid.html"; + } + + # Save the HTML. + &saveHTML ("$homedir/logs/$dir/$file"); + } +} + +sub saveHTML { + my $file = shift; + + # Save as HTML. + my (@lines) = reverse(@xhtml); + open (SAVE, ">$file"); + print SAVE "\n" + . "\n" + . "\n" + . "Cyan Chat Transcript | Perl CyanChat Client $VERSION\n" + . "\n" + . "\n" + . "\n" + . "\n" + . "\n" + . "
Transcript saved on " . localtime(time()) . "
\n\n" + . (join ("\n\n",@lines) ) + . "\n" + . ""; + close (SAVE); +} + +sub prefs { + if (exists $windows{__prefs__}) { + $windows{__prefs__}->focusForce; + } + else { + $helpPage = "general.html"; # The help page if we click for help. + + $windows{__prefs__} = $mw->Toplevel ( + -title => 'Preferences', + ); + $windows{__prefs__}->geometry ('580x400'); + $windows{__prefs__}->Icon (-image => $IMAGE{worlds}); + + $windows{__prefs__}->bind ('', sub { + delete $windows{__prefs__}; + }); + + # Create the button frame. + my $btnFrame = $windows{__prefs__}->Frame ( + )->pack (-side => 'bottom', -fill => 'x'); + my $prefsFrame = $windows{__prefs__}->Frame ( + )->pack (-side => 'bottom', -fill => 'both', -expand => 1); + + # Draw the window buttons. + $btnFrame->Button ( + -text => 'Help', + -command => sub { + &help ($helpPage); + }, + )->pack (-side => 'right', -padx => 10, -pady => 5); + $btnFrame->Button ( + -text => ' Apply ', + -command => sub { + # Save our configuration. + &savePrefs; + &bindChatTags; + }, + )->pack (-side => 'right', -padx => 15, -pady => 5); + $btnFrame->Button ( + -text => ' Cancel ', + -command => sub { + # Cancel anything we may have changed. + &initConfig("cancel"); + $windows{__prefs__}->destroy; + }, + )->pack (-side => 'right', -padx => 0, -pady => 5); + $btnFrame->Button ( + -text => ' OK ', + -command => sub { + # Commit the changes. + &savePrefs; + &bindChatTags; + $windows{__prefs__}->destroy; + }, + )->pack (-side => 'right', -padx => 5, -pady => 5); + + # Draw the tab frame. + my $tabFrame = $prefsFrame->NoteBook ( + -font => $FONT, + )->pack (-fill => 'both', -expand => 1); + + ###################### + ## General ## + ###################### + + my $genTab = $tabFrame->add ("general", + -label => "General", + -raisecmd => sub { + $helpPage = "general.html"; + }, + ); + + my $apLabFrame = $genTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Appearance', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $apFrame = $apLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labMainFont = $apFrame->Label ( + -text => 'Main Font Face:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 0, -sticky => 'ne'); + + $apFrame->Entry ( + -textvariable => \$config{dialogfont}, + -foreground => '#000000', + -background => '#FFFFFF', + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 0, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labMainFont, + -msg => "The font family used on most buttons and " + . "text entry boxes in the entire program.", + ); + + my $labFontSize = $apFrame->Label ( + -text => 'Font Size:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 1, -sticky => 'ne'); + + $apFrame->Entry ( + -textvariable => \$config{fontsize}, + -foreground => '#000000', + -background => '#FFFFFF', + -width => 4, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 1, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labFontSize, + -msg => "The font size (in pixels) of most buttons " + . "and text boxes in this program.", + ); + + my $labDialogFlow = $apFrame->Label ( + -text => 'Dialog Flow:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 2, -sticky => 'ne'); + + my $labReverse = $apFrame->Radiobutton ( + -variable => \$config{reversechat}, + -text => 'New messages on top (default CC behavior)', + -value => 1, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 2, -sticky => 'nw'); + + my $labNormal = $apFrame->Radiobutton ( + -variable => \$config{reversechat}, + -text => 'New messages on bottom', + -value => 0, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 3, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labDialogFlow, + -msg => "These options control where new messages appear " + . "in the dialog window.", + ); + $tipper->attach ($labReverse, + -msg => "New messages will appear on top, which mimics " + . "the default CC behavior.", + ); + $tipper->attach ($labNormal, + -msg => "New messages will appear on bottom, which mimics " + . "most traditional chat programs.", + ); + + my $labDisplayOpts = $apFrame->Label ( + -text => 'Display Options:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 4, -sticky => 'ne'); + + my $labOrientation = $apFrame->Checkbutton ( + -variable => \$config{orientation}, + -text => 'Reverse orientation (requires restart)', + -onvalue => 'bottom', + -offvalue => 'top', + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 4, -sticky => 'nw'); + + my $labNotify = $apFrame->Checkbutton ( + -variable => \$config{notifyanimate}, + -text => 'Animate the window titles when new messages arrive', + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 5, -sticky => 'nw'); + + my $labAutolog = $apFrame->Checkbutton ( + -variable => \$config{autologging}, + -text => 'Automatically log all transcripts', + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 6, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labDisplayOpts, + -msg => "Miscellaneous display options.", + ); + $tipper->attach ($labOrientation, + -msg => "When enabled, the text-entry box will appear below " + . "the chat dialog window, instead of on top (this " + . "mimics traditional chat programs).", + ); + $tipper->attach ($labNotify, + -msg => "When a new message arrives and PCCC is minimized, " + . "the title will animate to get your attention.", + ); + $tipper->attach ($labAutolog, + -msg => "When checked, all messages get automatically logged to " + . "the \"logs\" folder,\n" + . "sorted by date (yyyy-mm-dd) format.", + ); + + my $loginLabFrame = $genTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Nickname Settings', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $loginFrame = $loginLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labNickname = $loginFrame->Label ( + -text => "Default Nickname:", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 0, -sticky => 'ne'); + + $loginFrame->Entry ( + -textvariable => \$config{nickname}, + -foreground => '#000000', + -background => '#FFFFFF', + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 0, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labNickname, + -msg => "This nickname will be pre-entered in the Name: " + . "box on the chat window.", + ); + + my $labAutoJoin = $loginFrame->Checkbutton ( + -variable => \$config{autojoin}, + -text => "Automatically join chat when connected", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 0, -row => 1, -columnspan => 2, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labAutoJoin, + -msg => "When enabled, and when there's a Name entered, " + . "you will automatically join the chat when you " + . "connect.", + ); + + ###################### + ## Connection ## + ###################### + + my $connTab = $tabFrame->add ("conn", + -label => "Connection", + -raisecmd => sub { + $helpPage = "connection.html"; + }, + ); + + my $servLabFrame = $connTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Server Settings', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $servFrame = $servLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labHost = $servFrame->Label ( + -text => "CyanChat Host:", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 0, -sticky => 'ne',); + + $servFrame->Entry ( + -textvariable => \$config{chathost}, + -foreground => '#000000', + -background => '#FFFFFF', + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 0, -sticky => 'nw'); + + $servFrame->Label ( + -text => "Default: cho.cyan.com", + -font => $FONT, + )->grid (-column => 2, -row => 0, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labHost, + -msg => "The server (host) name of a CyanChat server.", + ); + + my $labPort = $servFrame->Label ( + -text => "Port:", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 1, -sticky => 'ne'); + + $servFrame->Entry ( + -textvariable => \$config{chatport}, + -foreground => '#000000', + -background => '#FFFFFF', + -width => 20, + -font => $FONT, + -highlightthickness => 0, + )->grid (-column => 1, -row => 1, -sticky => 'nw'); + + $servFrame->Label ( + -text => "Default: 1812\n" + . "Testing: 1813", + -font => $FONT, + )->grid (-column => 2, -row => 1, -sticky => 'nw'); + + # Balloon Tooltip. + $tipper->attach ($labPort, + -msg => "The port number that the CC server listens on.", + ); + + my $labAutoConnect = $servFrame->Checkbutton ( + -variable => \$config{autoconnect}, + -text => "Automatically connect when PCCC starts", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 2, -columnspan => 3, -sticky => 'nw'); + + my $labReconnect = $servFrame->Checkbutton ( + -variable => \$config{reconnect}, + -text => "Attempt to reconnect when disconnected", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 3, -columnspan => 3, -sticky => 'nw'); + + # Balloon Tooltips. + $tipper->attach ($labAutoConnect, + -msg => "When checked, PCCC will attempt to connect to CyanChat " + . "when it starts up.", + ); + $tipper->attach ($labReconnect, + -msg => "When checked, PCCC will attempt once to reconnect to the " + . "server is the connection is interrupted.", + ); + + ###################### + ## Color Scheme ## + ###################### + + my $colorTab = $tabFrame->add ("colors", + -label => "Colors", + -raisecmd => sub { + $helpPage = "colors.html"; + }, + ); + + my $colLabFrame = $colorTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Chat Colors', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'both', -expand => 1); + + my $colFrame = $colLabFrame->Scrolled ("Pane", + -scrollbars => 'e', + )->pack (-side => 'top', -fill => 'both', -expand => 1, -pady => 2); + + # Draw the colors. + my @types = ( + "h::PCCC Interface", + + "Window Background Color::windowbg", + "Window Text Color::windowfg", + "Button Background Color::buttonbg", + "Button Text Color::buttonfg", + "Textbox Background Color::inputbg", + "Textbox Text Color::inputfg", + "Disabled Text Color::disabledfg", + + "h::Chat Colors", + "Dialog Window Background::background", + "Main Chat Text::foreground", + "Who List Background::whobg", + "Hyperlinks::linkcolor", + "Private Messages::privatecolor", + "Action Messages::actioncolor", + + "h::Nickname Colors", + + "Normal Nicknames::usercolor", + "My Nickname Echo::echocolor", + "Cyan Staff::admincolor", + "Special Guests::guestcolor", + "ChatServer::servercolor", + "ChatClient::clientcolor", + ); + + my $colorRow = 0; + my %colorButtons = (); + foreach my $type (@types) { + my ($label,$var) = split(/::/, $type, 2); + + # Headers? + if ($label eq "h") { + # Draw the header in a significant style. + $colFrame->Label ( + -text => $var, + -relief => 'sunken', + -border => 2, + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => $colorRow, + -columnspan => 2, -sticky => 'ew', -ipady => 2); + } + else { + # Draw the label first. + $colFrame->Label ( + -text => "$label:", + -font => $FONT, + )->grid (-column => 0, -row => $colorRow, -sticky => 'e'); + + # Now draw the color preview. + $colorButtons{$var} = $colFrame->Button ( + -text => "xxxxxx", + -font => $FONT, + -foreground => $config{$var}, + -background => $config{$var}, + -activeforeground => $config{$var}, + -activebackground => $config{$var}, + -command => [ sub { + my $var = shift; + my $new = $windows{__prefs__}->chooseColor ( + -title => 'Choose Color', + -initialcolor => $config{$var}, + ); + + return unless defined $new; + $config{$var} = $new; + + $colorButtons{$var}->configure ( + -foreground => $new, + -background => $new, + -activeforeground => $new, + -activebackground => $new, + ); + }, $var ], + )->grid (-column => 1, -row => $colorRow, -sticky => 'nw'); + } + $colorRow++; + } + + ###################### + ## Ignored Users ## + ###################### + + my $ignoreTab = $tabFrame->add ("ignore", + -label => "Ignored Users", + -raisecmd => sub { + &refreshIgnoreLists(); + $helpPage = "ignorelist.html"; + }, + ); + + my $ignoreLabFrame = $ignoreTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Ignored Users', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $ignoreFrame = $ignoreLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + $ignoreFrame->Label ( + -text => 'Use this window to modify your ignore list.', + -font => $FONT, + )->grid (-column => 0, -row => 0, -sticky => 'w'); + + my $labOnlineUsers = $ignoreFrame->Label ( + -text => 'Online Users:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 1, -sticky => 'n'); + + my $labIgnoredUsers = $ignoreFrame->Label ( + -text => 'Ignored Users:', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 1, -row => 1, -sticky => 'n'); + + $pOnlineList = $ignoreFrame->Scrolled ("Listbox", + -scrollbars => "e", + -background => '#FFFFFF', + -foreground => '#000000', + -highlightthickness => 0, + -selectbackground => '#FFFF00', + -selectforeground => '#000000', + -height => 6, + -width => 20, + -font => $FONT, + )->grid (-column => 0, -row => 2, -sticky => 'n'); + + $pIgnoreList = $ignoreFrame->Scrolled ("Listbox", + -scrollbars => "e", + -background => '#FFFFFF', + -foreground => '#000000', + -highlightthickness => 0, + -selectbackground => '#FFFF00', + -selectforeground => '#000000', + -height => 6, + -width => 20, + -font => $FONT, + )->grid (-column => 1, -row => 2, -sticky => 'n'); + + my $btnIgnore = $ignoreFrame->Button ( + -text => 'Ignore Selected', + -font => $FONT, + -command => sub { + my $selected = $pOnlineList->get ( + ($pOnlineList->curselection)[0] + ); + print "selected: $selected\n"; + my ($name,$addr) = split(/:/, $selected, 2); + &ignoreUser (undef,$name); + &refreshIgnoreLists(); + }, + )->grid (-column => 0, -row => 3, -sticky => 'n'); + + my $btnUnignore = $ignoreFrame->Button ( + -text => 'Unignore Selected', + -font => $FONT, + -command => sub { + my $selected = $pIgnoreList->get ( + ($pIgnoreList->curselection)[0] + ); + print "selected: $selected\n"; + my ($name,$addr) = split(/:/, $selected, 2); + &ignoreUser (undef,$name); + &refreshIgnoreLists(); + }, + )->grid (-column => 1, -row => 3, -sticky => 'n'); + + my $btnRefresh = $ignoreFrame->Button ( + -text => 'Refresh Lists', + -font => $FONT, + -command => \&refreshIgnoreLists, + )->grid (-column => 0, -row => 4, -sticky => 'w'); + + my $ignOpts = $ignoreFrame->Pane ( + )->grid (-column => 0, -row => 5, -columnspan => 2, -sticky => 'nw'); + + my $labStickyIgnore = $ignOpts->Checkbutton ( + -variable => \$config{stickyignore}, + -text => 'Remember my ignore list.', + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 1, -sticky => 'w'); + + my $labMutualIgnores = $ignOpts->Checkbutton ( + -variable => \$config{ignoreback}, + -text => "Perform mutual ignores", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 2, -sticky => 'w'); + + my $labLoudIgnore = $ignOpts->Checkbutton ( + -variable => \$config{loudignore}, + -text => "Tell me when somebody ignores me", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 1, -row => 1, -sticky => 'w'); + + my $labSendIgnore = $ignOpts->Checkbutton ( + -variable => \$config{sendignore}, + -text => "Send server ignore command when ignoring users", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 1, -row => 2, -sticky => 'w'); + + # Balloon Tooltips. + $tipper->attach ($labOnlineUsers, + -msg => "This listbox displays the current online users.", + ); + $tipper->attach ($labIgnoredUsers, + -msg => "This listbox displays the users you're currently ignoring.", + ); + $tipper->attach ($btnIgnore, + -msg => "Click this button to ignore the selected user.", + ); + $tipper->attach ($btnUnignore, + -msg => "Click this button to remove the selected user from your " + . "ignore list.", + ); + $tipper->attach ($btnRefresh, + -msg => "Click this button to refresh the lists on this page.", + ); + $tipper->attach ($labStickyIgnore, + -msg => "Enable this option to save your Ignore List after you " + . "shut down PCCC.", + ); + $tipper->attach ($labMutualIgnores, + -msg => "Automatically ignore everyone who ignores us.", + ); + $tipper->attach ($labLoudIgnore, + -msg => "When enabled, show a message in chat when somebody " + . "ignores you.", + ); + $tipper->attach ($labSendIgnore, + -msg => "When enabled, send the actual Ignore command to " + . "the CyanChat server (which can then notify the " + . "target that you are ignoring them).\n" + . "Server-side ignores can't be unignored without " + . "disconnecting from the server.", + ); + + ###################### + ## Sound Effects ## + ###################### + + my $sfxTab = $tabFrame->add ("sfx", + -label => "Sounds", + -raisecmd => sub { + $helpPage = "sounds.html"; + }, + ); + + my $sfxLabFrame = $sfxTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Sound Effects', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $sfxFrame = $sfxLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labPlaySounds = $sfxFrame->Checkbutton ( + -variable => \$config{playsounds}, + -text => "Enable Sound Effects", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 0, -sticky => 'w'); + + my $eventLabFrame = $sfxTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Events', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $eventFrame = $eventLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labJoinSound = $eventFrame->Checkbutton ( + -variable => \$config{playjoin}, + -text => "When a user joins the room...", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 0, -sticky => 'w'); + + my $labLeaveSound = $eventFrame->Checkbutton ( + -variable => \$config{playleave}, + -text => "When a user exits the room...", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 1, -sticky => 'w'); + + my $labPublicSound = $eventFrame->Checkbutton ( + -variable => \$config{playpublic}, + -text => "When a message is received...", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 2, -sticky => 'w'); + + my $labPrivateSound = $eventFrame->Checkbutton ( + -variable => \$config{playprivate}, + -text => "When a private message is received...", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 3, -sticky => 'w'); + + for (my $i = 0; $i <= 3; $i++) { + $eventFrame->Label ( + -text => "play", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 1, -row => $i, -sticky => 'e'); + } + + # Create a list of wav files from the sfx folder. + opendir (DIR, "./sfx"); + my @wavs = sort(grep(/\.wav$/i, readdir(DIR))); + closedir (DIR); + + my $i = 0; + foreach (qw(joinsound leavesound publicsound privatesound)) { + my $tmp = $eventFrame->BrowseEntry ( + -variable => \$config{$_}, + -font => $FONT, + -options => [ + @wavs, + ], + )->grid (-column => 2, -row => $i, -sticky => 'w'); + $tmp->Subwidget("entry")->configure ( + -background => '#FFFFFF', + -foreground => '#000000', + -font => $FONT, + -width => 10, + ); + $eventFrame->Button ( + -text => 'Play', + -font => $FONT, + -command => [ sub { + my $sound = shift; + if (length $config{$sound}) { + push (@PLAYSOUNDS,$config{$sound}); + } + }, $_ ], + )->grid (-column => 3, -row => $i, -sticky => 'w'); + + $i++; + } + + ###################### + ## Miscellaneous ## + ###################### + + my $miscTab = $tabFrame->add ("misc", + -label => "Miscellaneous", + -raisecmd => sub { + $helpPage = "misc.html"; + }, + ); + + my $progLabFrame = $miscTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'External Programs', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $progFrame = $progLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labBrowser = $progFrame->Label ( + -text => "Web Browser Command:", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 0, -sticky => 'e'); + + my $labBrowserCmd = $progFrame->BrowseEntry ( + -variable => \$config{browser}, + -options => [ + "start", + "htmlview", + "open", + ], + -font => $FONT, + )->grid (-column => 1, -row => 0, -sticky => 'w'); + $labBrowserCmd->Subwidget("entry")->configure ( + -background => '#FFFFFF', + -foreground => '#000000', + -font => $FONT, + -width => 20, + ); + + my $labMPlayer = $progFrame->Label ( + -text => "Command-line Media Player:", + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->grid (-column => 0, -row => 1, -sticky => 'e'); + + # For Windows users, don't even show this option. + if ($^O =~ /win(32|64)/i) { + $progFrame->Label ( + -text => "Win32::MediaPlayer", + -font => $FONT, + )->grid (-column => 1, -row => 1, -sticky => 'w'); + } + else { + my $labMPlayerCmd = $progFrame->Entry ( + -textvariable => \$config{mediaplayer}, + -width => 20, + -font => $FONT, + )->grid (-column => 1, -row => 1, -sticky => 'w'); + } + + # Balloon Tooltip. + $tipper->attach ($labBrowser, + -msg => "Type or select the command-line program for " + . "viewing web pages.\n" + . "Windows should use `start`\n" + . "Linux should use `htmlview`\n" + . "Mac should use `open`", + ); + + my $miscLabFrame = $miscTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Miscellaneous Options', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $miscFrame = $miscLabFrame->Pane ( + )->pack (-side => 'left', -padx => 15); + + my $labImWindows = $miscFrame->Checkbutton ( + -variable => \$config{imwindows}, + -text => "Show private messages in new windows", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 0, -sticky => 'w'); + + my $labIgnoreServer = $miscFrame->Checkbutton ( + -variable => \$config{blockserver}, + -text => "Ignore private messages from ChatServer", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 1, -sticky => 'w'); + + my $labAction = $miscFrame->Checkbutton ( + -variable => \$config{autoact}, + -text => "Show *...* messsages as /me actions", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 5, -sticky => 'w'); + + my $labTypo = $miscFrame->Checkbutton ( + -variable => \$config{loudtypo}, + -text => "Highlight typo corrections", + -onvalue => 1, + -offvalue => 0, + -font => $FONT, + )->grid (-column => 0, -row => 6, -sticky => 'w'); + + # Balloon Tooltip. + $tipper->attach ($labIgnoreServer, + -msg => "Ignores private messages sent by [ChatServer] " + . "(useful when on debug port 1813)", + ); + $tipper->attach ($labAction, + -msg => "Messages starting and ending with a * will get " + . "displayed as a \"/me\" style message.", + ); + $tipper->attach ($labTypo, + -msg => "Messages starting with * will get displayed as a " + . "\"typo correction\" message.", + ); + + my $defLabFrame = $miscTab->LabFrame ( + -labelside => 'acrosstop', + -label => 'Revert to Default Settings', + -font => [ + @{$FONT}, + -weight => 'bold', + ], + )->pack (-fill => 'x'); + + my $defFrame = $defLabFrame->Pane ( + )->pack (-side => 'top', -fill => 'x', -expand => 1, -padx => 15); + + $defFrame->Label ( + -text => "Click the button below to revert back to the " + . "default configuration:", + -font => $FONT, + )->pack; + + $defFrame->Button ( + -text => "Reset Configuration", + -font => $FONT, + -command => sub { + # Delete the config file. + if (-f "$homedir/config.txt") { + unlink ("$homedir/config.txt"); + } + # Reload configuration. + &initConfig("cancel"); + &bindChatTags(); + + # Destroy this window. + $windows{__prefs__}->destroy; + + # Reload this window. + &prefs(); + }, + )->pack; + } +} + +sub refreshIgnoreLists { + # Sort and populate the lists. + $pOnlineList->delete ('0','end'); + $pIgnoreList->delete ('0','end'); + + my @lsonline = sort { $a cmp $b } keys %online; + my @lsignore = sort { $a cmp $b } keys %ignore; + + # Populate the online users list, skip ignored users. + foreach my $nln (@lsonline) { + next if exists $ignore{$nln}; + + # Get the user's info. + my ($level,$addr) = split(/\;/, $online{$nln}, 2); + + $pOnlineList->insert ('end',"$nln:$addr"); + } + + # Populate the ignore users list. + foreach my $nln (@lsignore) { + print "add ignore: $nln ($online{$nln})\n"; + my ($level,$addr) = split(/\;/, $online{$nln}, 2); + $pIgnoreList->insert ('end',"$nln:$addr"); + } +} + +sub playSound { + my $option = shift; + my $sfx = join ("",$option,"sound"); + my $check = join ("","play",$option); + + # See if we're allowed to play this sound. + my $allowed = 1; + + # If the global configuration is disabled, don't allow. + if ($config{playsounds} == 0) { + $allowed = 0; + } + + # If we're muting the sounds temporarily, don't allow. + if ($mutesfx == 1) { + $allowed = 0; + } + + # If this particular event is disabled, don't allow. + if ($config{$check} == 0) { + $allowed = 0; + } + + # If the file doesn't exist, don't allow. + if (!-f "./sfx/$config{$sfx}") { + $allowed = 0; + } + + # If allowed, play it. + if ($allowed) { + push (@PLAYSOUNDS,$config{$sfx}); + } + + return $allowed; +} + +sub help { + my $page = shift || "index.html"; + + if (exists $windows{__help__}) { + $windows{__help__}->focusForce; + &helpPage ($page); + } + else { + @helphistory = (); + + $windows{__help__} = $mw->Toplevel ( + -title => 'PCCC Help', + ); + $windows{__help__}->geometry ('550x400'); + $windows{__help__}->Icon (-image => $IMAGE{worlds}); + + $windows{__help__}->bind ('', sub { + $htmlhelp = undef; + delete $windows{__help__}; + }); + + # Draw the toolbar frame. + my $tbFrame = $windows{__help__}->Frame ( + -borderwidth => 2, + -relief => 'raised', + )->pack (-side => 'top', -fill => 'x'); + + # Toolbar buttons. + my $btnContents = $tbFrame->Button ( + -text => "Contents", + -font => $FONT, + -command => sub { + &helpPage ("index.html"); + }, + )->pack (-side => 'left'); + my $btnBack = $tbFrame->Button ( + -text => "Back", + -font => $FONT, + -command => sub { + &helpBack; + }, + )->pack (-side => 'left'); + my $btnExit = $tbFrame->Button ( + -text => "Close", + -font => $FONT, + -command => sub { + $windows{__help__}->destroy; + }, + )->pack (-side => 'left'); + + # Main frame. + my $mainFrame = $windows{__help__}->Frame ( + )->pack (-fill => 'both', -expand => 1); + + # HTML widget. + $htmlhelp = $mainFrame->Scrolled ("HyperText", + -scrollbars => 'e', + -wrap => 'word', + -titlecommand => \&helpTitle, + -linkcommand => \&helpLink, + )->pack (-fill => 'both', -expand => 1); + + # Show the requested page. + &helpPage ($page); + } +} + +sub helpPage { + my $page = shift; + my $nohistory = shift || 0; + + if (!-f "./docs/$page") { + $page = "404.html"; + } + + open (PAGE, "./docs/$page"); + my @html = ; + close (PAGE); + chomp @html; + + my $code = join ("\n",@html); + $code =~ s/%VERSION%/$VERSION/ig; + $code =~ s/%DATE%/$MODIFIED/ig; + $code =~ s/%CC%/$Net::CyanChat::VERSION/ig; + $code =~ s/%HTML%/$Tk::HyperText::VERSION/ig; + + print "Load help document $page\n"; + unless ($nohistory) { + push (@helphistory, $page); + shift(@helphistory) until scalar(@helphistory) <= 25; + } + + if (defined $htmlhelp) { + $htmlhelp->clear; + $htmlhelp->insert ('end',$code); + } +} + +sub helpBack { + if (scalar(@helphistory)) { + my $back = pop(@helphistory); + &helpPage ($back,1); + } +} + +sub helpTitle { + my ($widget,$title) = @_; + if (defined $windows{__help__}) { + if (length $title) { + $windows{__help__}->title ("$title - PCCC Help"); + } + else { + $windows{__help__}->title ("PCCC Help"); + } + } +} + +sub helpLink { + my ($widget,$href,$target) = @_; + + if ($target eq "_blank") { + push (@HYPERLINKLIST, $href); + } + else { + &helpPage ($href); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b34902 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# Perl CyanChat Client + +## I. About PCCC + +Perl CyanChat Client 2.x is a complete rewrite from the original +1.x versions. The new client uses `Net::CyanChat` to connect to +the CyanChat servers instead of having the code included within +PCCC's own code. + +PCCC 1.x was actually written prior to Net::CyanChat which I +created AFTER making PCCC 1.x, so the new PCCC makes up for that. + +## II. About CyanChat + +CyanChat is the name of a chat room which is owned by Cyan Worlds, +Inc. (formerly known simply as Cyan). They created some really good +adventure games named Myst and Riven (and Myst III and then Myst IV +and V too), as well as a few other spinoff games such as Uru and +RealMyst. + +The chat server was programmed by Mark Deforest of Cyan Worlds. The +chat room was created so that fans of Cyan could have a place to +meet and discuss their games and novels and interact with other fans. +The "CyanChat Community" is made up of a small number of members who +have been with CyanChat for years and years (I first went to CyanChat +like six years ago and the same group of people are still here today!) + +The official homepage to CyanChat is: http://cho.cyan.com/chat/ + +## III. CyanChat Rules and Policies + +Official Rules Page: http://cho.cyan.com/chat/rules.html + +* Be respectful of and sensitive to others. +* Please, no platform wars ("my computer is better than yours"). +* Keep it "G" rated; in other words, suitable for family viewing. +* No flooding, in other words, filling the screen with junk. +* But most of all HAVE FUN! + +### A. Impersonating + +No name or handle is reserved for any one person. +However, purposely impersonating someone for personal +gain or in disrespect of the person being impersonated +will not be tolerated. So, please try to find a +unique name for yourself. + +### B. Being Banned + +The CyanChat server has a bad language filter that +watches all the messages being sent. If it detects +that you have used bad language, depending how severe, +it might automatically ban you from using CyanChat, +ban you for a day or just censor the message. Once +you have been banned you will get a message when you +start CyanChat that your IP address has been blocked +from using CyanChat. + +### C. Getting Unbanned + +There are many reasons why an IP address might be banned +from CyanChat, some reasons are accidental, such as misspelling +a word. If you've gotten accidentally banned, e-mail markd@cyan.com +with the IP address that is banned. But one thing to +remember is that I have a log of all the bannings (and what +was said) and its usually quite obvious, so don't try the +"accident" angle unless it really was. + +## IV. Configuring PCCC + +After running PCCC for the first time, you can configure CyanChat +by choosing "Edit -> Preferences". The client assumes a number of default +preferences, which you can change. If you want to restore them to their +defaults, either delete "config.txt" and restart the program, or click +"Restore Defaults" in the preferences window. + +## V. Using PCCC + +When you open PCCC, it should connect automatically unless you specified +that it shouldn't. In that case, click "Connection -> Connect" on the menu +bar to connect to CyanChat. + +When connected, you will receive a lot of messages from ChatServer. These +are introduction messages. + +Type a nickname for yourself in the box next to the word "Name:" toward +the top of the window. Then click "Join Chat" to enter the room. Note that +nicknames can be no longer than 20 characters and that they can't contain +the pipe symbol `|`. + +Write messages into the long text box above the chat dialog space. To send +a private message to somebody, there are three options you can use: + +1. Write a message into the normal message space, then single-click + the target's name from the Who List, and click "Send Private" +2. Double-click a user's name from the Who List to open a Private + Message window. Type your message into this window and hit Enter. +3. In the normal message space, type `/whisper `, + substituting a user's name for `` and a message for ``. + +To exit the chat room, click the "Exit Chat" button. To disconnect from +CyanChat, click "Connection -> Disconnect". Doing this will also sign you +out if you are currently signed in to the chat room. + +Exiting PCCC via "File -> Exit" will also sign you out and disconnect you +where applicable. Closing out of the program in any other means will result +in a "disconnect", where CyanChat will simply tell the other users that you +were disconnected rather than that you signed out properly. + +## VI. Installation + +Perl CyanChat Client should work fine on all operating systems. It mostly +uses the standard Tk modules from Tk version 804.027 + +In addition to the standard Tk modules, the following nonstandard modules +may need to be installed: + + Net::CyanChat 0.04 or higher. + +These modules have been included in the standard distribution of +Perl CyanChat Client. + +## VII. License and Copyright + + Perl CyanChat Client + Copyright (C) 2006-13 Noah Petherbridge + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/balloon.gif b/balloon.gif new file mode 100644 index 0000000000000000000000000000000000000000..083fbd7d1c08afb076612e0dd7561b401182ed04 GIT binary patch literal 726 zcmV;{0xA7RNk%w1VITk?0OkMy0|Nv3`T70*{r2|u`1tq&0s=unK?DQ@0RaKu-`}aJ zsjjZB_xJa-w6sG*LmCmLnr0 z%*@R4^75ddpeiaVtgNhVZfz`&7_kvTaz5D*aO=jSywHEnHe+}zw$R8-#H z-tqDAb#--}ot?bAyi80?4Gj&ds;bS+&4Gb|GBPsl?d{Rg(Ym_2?Ck8u#>QY^U`a_y z4h{~pv$GNs60xzdva+&WU0qI2PNJfsHa0f<`}-aq9?#Ful$4av(9rYq^H^9|hK7cD zd3j1oO5x$*mzS5t#l^n9z8oAJj*gD7u(0Cd;&E|tg@uKio15U^;K#?u6ciLqO-;$k z$p;4q4-XG4EG%YbW{8N0)6>(RpPvg03;+NB000000000000000A^8LV00000EC2ui z03ZM$000O7fP8|3goTEOh>41ejE#5v{ByI$IoSZ6DDHK5n~A)MMow#4}}N^bs=xBz`?H;4HO#y2!bt64HyH$(7+NHL1G7dC@@CR+`%gi zJuMjtS>5KG5e#N=OXu#KE*cE)?`Z<`?n3zPWUl+&7n=O*R)KEm6R*F!UrzrxgSS3?M)tfK8hW2Ds?(;)K?I3=oD^WGfIR zjzkbMhM?gAc25o<2MXu~z_)J!1j!KX-~d)a7Y`RUNFqc-&AkpY+Ek&lxU%KTm@{kM IJSY$VJI6gnT>t<8 literal 0 HcmV?d00001 diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..e8dba80 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,13 @@ + + +Error 404 + + + + +

404 Error

+ +The page you requested was not found. + + + diff --git a/docs/about.html b/docs/about.html new file mode 100644 index 0000000..556e742 --- /dev/null +++ b/docs/about.html @@ -0,0 +1,27 @@ + + +About PCCC + + + + +

About Perl CyanChat Client

+ +The Perl CyanChat Client (PCCC) is a program used for chatting on the + Cyan Worlds chat room. + It was written using the programming language + Perl, and the Tk graphical + user interface. + +

About CyanChat

+ +CyanChat is the name of the chat room of Cyan Worlds, Inc., where the fans of + Cyan's games and novels can meet and chat with one another. + +

PCCC Version

+ +You are running Perl CyanChat Client version %VERSION% (%DATE%).

+Running on Net::CyanChat v. %CC% and Tk::HyperText v. %HTML% + + + diff --git a/docs/action.html b/docs/action.html new file mode 100644 index 0000000..ae19431 --- /dev/null +++ b/docs/action.html @@ -0,0 +1,36 @@ + + +Action Messages + + + + +

Action Messages

+ +"Action Messages" are a bit of an extension to the CyanChat protocol. It's not + actually supported on the CC server, but several home-made CC clients support + this. + +

IRC-Style /me Actions

+ +Typing in "/me <action>" in the message space is the standard way of sending + an action message. To all clients who support the feature, your message should appear + special on their client. To those which don't support the feature, they'll simply + see "/me <action>" from you. + +

Automatic Actions

+ +There is an option on the Miscellaneous tab of the Preferences + window which will automatically display certain messages in the "action" format.

+ +When enabled, a message that begins and ends with asterisks (*) will automatically be + displayed as action messages. + +

Typo Messages

+ +Typo messages are similar to action messages in that they display in a special color. + When a message is received that begins with a * but doesn't end with one, the message + is displayed normally, but the text is displayed in the "action color" + + + diff --git a/docs/colors.html b/docs/colors.html new file mode 100644 index 0000000..ed45e51 --- /dev/null +++ b/docs/colors.html @@ -0,0 +1,33 @@ + + +Color Preferences + + + + +

Preferences: Colors

+ +

Chat Colors

+ +
+ PCCC Interface +
+ These colors will determine elements on the main window of PCCC, separate from + the chat dialog window within. +
+ + Chat Colors +
+ These control the general color scheme of the dialog window, such as text + colors related to private messages, actions, and hyperlinks. +
+ + Nickname Colors +
+ These control the colors of different nickname types within the dialog + window. +
+
+ + + diff --git a/docs/connection.html b/docs/connection.html new file mode 100644 index 0000000..aa907b8 --- /dev/null +++ b/docs/connection.html @@ -0,0 +1,36 @@ + + +Connection Preferences + + + + +

Preferences: Connection

+ +

Server Settings

+ +
+ CyanChat Host +
+ The name of the CC server. Default is cho.cyan.com +
+ + Port +
+ The port that the CC server will listen to you from. Default is 1812, + while port 1813 is used for testing. +
+ + Automatically connect when PCCC starts +
+ When checked, PCCC will automatically connect to the server when it starts. +
+ + Attempt to reconnect when disconnected +
+ When checked, PCCC will attempt to reconnect when it has been disconnected. +
+
+ + + diff --git a/docs/console.html b/docs/console.html new file mode 100644 index 0000000..5664772 --- /dev/null +++ b/docs/console.html @@ -0,0 +1,26 @@ + + +Chat Console + + + + +

Chat Console

+ +The Chat Console (also known as the Debug Window) is a small window that + monitors all incoming and outgoing TCP packets. It's useful as a debugging tool, + and to see what's really going on in the background during your CC connection.

+ +Messages sent from your client to the server appear in red + text, while messages received from the server are in blue.

+ +Note that the chat console does not automatically scroll so long as you have + the window opened. This is actually more useful for debugging purposes than having + the window keep scrolling to the bottom constantly. However, when you first open the + window, it will automatically jump to the bottom.

+ +To make the window disappear, click the "Dismiss" button. The "X" button on the title + bar won't close the window. + + + diff --git a/docs/cyan.html b/docs/cyan.html new file mode 100644 index 0000000..2da3600 --- /dev/null +++ b/docs/cyan.html @@ -0,0 +1,25 @@ + + +Cyan Staff + + + + +

Cyan Staff

+ +The Cyan Staff (also known as Cyanites or Cyantists) are the staff members of the + company Cyan Worlds. When they enter the chat room, their "link in" message will + read: "<links in from Cyan Worlds, Inc.>"

+ +Additionally, their nicknames will be placed in the "Cyan & Guests" list instead + of the standard Who List, and their names will appear in + cyan text. + +

Special Guests

+ +Occasionally, a Cyantist will promote a regular user to the level of "Special Guest". + Special Guests also appear in the special "Cyan & Guests" list, and their nicknames + will appear in orange text. + + + diff --git a/docs/details.html b/docs/details.html new file mode 100644 index 0000000..f59faac --- /dev/null +++ b/docs/details.html @@ -0,0 +1,15 @@ + + +Connection Details + + + + +

Connection Details

+ +This window is relatively useless. It lists the server, port, and connection + status. If it recognizes the server or the port to be CC standard, it will + display a notification next to it. + + + diff --git a/docs/general.html b/docs/general.html new file mode 100644 index 0000000..2c5dd22 --- /dev/null +++ b/docs/general.html @@ -0,0 +1,82 @@ + + +General Preferences + + + + +

Preferences: General

+ +

Appearance

+ +
+ Main Font Face +
+ This font face is used in the chat dialog window, as well as on all + of the GUI elements everywhere else in the program (on labels, buttons, + text boxes, etc.) +
+ + Font Size +
+ Specify the main font size, in pixels. +
+ + Dialog Flow +
+ New messages on top (default CC behavior) +
+ When new messages are received, they'll appear at the top + of the dialog window. This is how the standard client + behaves. +
+ New messages on bottom +
+ This mimics the behavior of most other chat programs. New + messages will appear on the bottom. +
+
+ + Display Options +
+ Reverse orientation +
+ When checked, the message space will start appearing beneath + the dialog window, like most other chat programs. Changing + this option requires a restart of the program. +
+ + Animate the window titles when new messages arrive +
+ When checked, the titles of the main window and message + windows will animate when a new message arrives while the + window is out of focus or minimized. +
+ + Automatically log all transcripts +
+ When checked, all messages are automatically logged to + HTML files. They're saved in your profile directory. See + User Profiles for more information. +
+
+
+ +

Nickname Settings

+ +
+ Default Nickname: +
+ This nickname will be automatically filled in to the "Name" box + when you start up PCCC. +
+ + Automatically join chat when connected +
+ When checked, you will automatically join the chat room when you + connect (provided you have a nickname entered at the time). +
+
+ + + diff --git a/docs/ignore.html b/docs/ignore.html new file mode 100644 index 0000000..edb6b22 --- /dev/null +++ b/docs/ignore.html @@ -0,0 +1,24 @@ + + +Ignoring Users + + + + +

Ignoring Users

+ +If a user is being annoying, you can ignore them by selecting their name on the + Who List and pressing the "Ignore" button below.

+ +You can also ignore/unignore users by going to the Ignored + Users tab on the Preferences window.

+ +You can unignore users by selecting their name on the Who List and pressing the + "Ignore" button again. + +

Advanced Ignore Options

+ +See the Ignored Users page for more information. + + + diff --git a/docs/ignorelist.html b/docs/ignorelist.html new file mode 100644 index 0000000..3d05ec6 --- /dev/null +++ b/docs/ignorelist.html @@ -0,0 +1,59 @@ + + +Ignored Users Preferences + + + + +

Preferences: Ignored Users

+ +

Ignored Users

+ +
+ Online Users +
+ This listbox displays all of the users currently signed in to CyanChat + (but not the ones you've ignored). Select a user from the list and press + "Ignore Selected" to move them to the Ignored Users list. +
+ + Ignored Users +
+ This listbox displays all of the users you've ignored. Select a user from + the list and choose "Unignore Selected" to stop ignoring them. +
+ + Refresh Lists +
+ This button will refresh the listboxes. The lists are also refreshed + each time you bring focus to the "Ignored Users" tab. +
+ + Remember my ignore list +
+ When checked, your ignore list will be saved when you exit PCCC and + be reloaded when you start it again. +
+ + Perform mutual ignores +
+ When checked, PCCC will automatically ignore any users who ignore us. +
+ + Tell me when somebody ignores me +
+ When checked, you will receive a notification in the dialog window + when somebody has ignored you. +
+ + Send server ignore command when ignoring users +
+ When checked, ignoring a user will send a standard CC ignore command + to the server. What this does is tells the server to relay your + ignore to the client in question, so that they may perform a mutual + ignore. +
+
+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..2a049ad --- /dev/null +++ b/docs/index.html @@ -0,0 +1,41 @@ + + +Contents + + + + +

Perl CyanChat Client

+ +Getting Started

+ +> About Perl CyanChat Client
+> CyanChat Rules

+ +Using PCCC

+ +> General Usage
+> User Profiles
+> Private Messaging
+> Action Messages
+> Ignoring Users
+> Cyan Staff
+> Save Transcript

+ +Configuration

+ +> General
+> Connection
+> Colors
+> Ignored Users
+> Sounds
+> Miscellaneous

+ +Advanced Features

+ +> Chat Console
+> Send Raw Command
+> Connection Details + + + diff --git a/docs/misc.html b/docs/misc.html new file mode 100644 index 0000000..f8a2ea4 --- /dev/null +++ b/docs/misc.html @@ -0,0 +1,73 @@ + + +Miscellaneous Preferences + + + + +

Preferences: Miscellaneous

+ +

External Programs

+ +
+ Web Browser Command +
+ This is the command-line program used for launching your web + browser. The dropdown list includes the most likely programs + for each operating system, but you can type in a custom command + too (ex: "firefox")

+ + Windows users should be fine with start
+ Linux and Unix users should use htmlview
+ Mac OS users should use open +

+ + Command-line Media Player +
+ This is the command-line program used for playing sound effects. + This is only applicable to Linux and Mac users. On Windows, the + module Win32::MediaPlayer is used instead.

+ + Linux users should be fine with play +

+
+ +

Miscellaneous Options

+ +
+ Show private messages in new windows +
+ When checked, all private messages will appear in their own "IM" + windows. Disable this to get the standard CC behavior back. When + disabled, IM windows can only be created by double-clicking a user's + name in the list. +
+ + Ignore private messages from ChatServer +
+ Check this to ignore all private messages from ChatServer. This + is NOT RECOMMENDED. It's most useful on the test server, + where ChatServer echoes a confirmation of every little thing your + client does, but should not be used on the real server. +
+ + Show *...* messages as /me actions +
+ This option will automatically display messages as "/me" actions + if they start and end with asterisks. See + Action Messages. +
+ + Highlight typo corrections +
+ This option will automatically display "typo" messages uniquely. + See Action Messages. +
+
+ +

Revert to Default Settings

+ +This button will restore all of your configuration back to the defaults. + + + diff --git a/docs/pm.html b/docs/pm.html new file mode 100644 index 0000000..6b3b4d4 --- /dev/null +++ b/docs/pm.html @@ -0,0 +1,50 @@ + + +Private Messaging + + + + +

Private Messaging

+ +The CyanChat server supports the sending of private messages to other + participants of the chat room. There are three different ways to send private + messages: + +

The CyanChat Way

+ +The "CyanChat Way" is the method used in the standard CyanChat client. To send + a private message to somebody:

+ +1. Select the recipient's name from the Who List
+2. Type in a message to send to them.
+3. Press the "Send Private" button located beneath the Who List. + +

The IRC Way

+ +The "IRC Way" is the method that is used on IRC chat servers (CC is not based on + IRC, but IRC users might be more familiar with this method).

+ +1. Type in "/msg <recipeint> <message>" into the message space.
+Example: /msg Cuvou hello there!

+ +The aliases /whisper and /w may also be used. + +

The IM Way

+ +The final method, the "IM Way", is the method that is characteristic of Instant + Messaging programs. Simply double-click a user's name from the Who List and open + an Instant Message window with them. + +

Message Windows

+ +Unless you disable it, the sending or receiving of private messages will always + open a message window with that user. All later private messages received from + the same user will go to this same window.

+ +If you have disabled this, then the only way to open a message window is if you + open it yourself, by double-clicking a user's name. See + Miscellaneous Configuration Options. + + + diff --git a/docs/profile.html b/docs/profile.html new file mode 100644 index 0000000..445127e --- /dev/null +++ b/docs/profile.html @@ -0,0 +1,27 @@ + + +User Profiles + + + + +

User Profiles

+ +PCCC saves your configuration and logs to your "user profile directory". This directory + is your home directory, plus "PCCC" or ".pccc". To locate it, read on for a list of + common places for each operating system. + +

Windows XP

+ +
+C:\Documents and Settings\username\PCCC +
+ +

Linux and Unix

+ +
+/home/username/.pccc +
+ + + diff --git a/docs/raw.html b/docs/raw.html new file mode 100644 index 0000000..acf0364 --- /dev/null +++ b/docs/raw.html @@ -0,0 +1,29 @@ + + +Raw Commands + + + + +

Send Raw Command

+ +WARNING!!!

+ +This function of PCCC is VERY dangerous. The CyanChat server is very picky + about the messages it will receive from your client. If you send an improperly + formatted command, you may be banned from the server.

+ +I cannot be held responsible if you get yourself banned by using this feature. + +

Sending a Raw Command

+ +To send a raw command, type it into the text box on the "Send Raw Command" window. + Press "Send Command" to send it. The "Return" key will not send the command + (as of version 3.0), because accidentally tapping Return and sending an incomplete + command was an easy way to get banned.

+ +Press the "Spawn Debug Window" button to open the Chat + Console window. + + + diff --git a/docs/rules.html b/docs/rules.html new file mode 100644 index 0000000..be3349e --- /dev/null +++ b/docs/rules.html @@ -0,0 +1,44 @@ + + +Rules + + + + +

CyanChat Rules

+ +* Be respectful of and sensitive to others.
+* Please, no platform wars ("my computer is better than yours").
+* Keep it "G" rated; in other words, suitable for family viewing.
+* No flooding, in other words, filling the screen with junk.
+* But most of all HAVE FUN! + +

Impersonating

+ +No name or handle is reserved for any one person. However, purposely impersonating + someone for personal gain or in disrespect of the person being impersonated + will not be tolerated. So, please try to find a unique name for yourself. + +

Being Banned

+ +The CyanChat server has a bad language filter that watches all the messages being + sent. If it detects that you have used bad language, depending how severe, + it might automatically ban you from using CyanChat, ban you for a day + or just censor the message. Once you have been banned you will get a message + when you start CyanChat that your IP address has been blocked from using CyanChat. + +

Getting Unbanned

+ +There are many reasons why an IP address might be banned from CyanChat, some reasons + are accidental, such as misspelling a word. If you've gotten accidentally banned, + e-mail markd@cyan.com with the + IP address that is banned. But one thing to remember is that MarkD has a log of all + the bannings (and what was said) and it's usually quite obvious, so don't try the + "accident" angle unless it really was.

+ +Offical CC Rules Page: + +http://cho.cyan.com/chat/rules.html + + + diff --git a/docs/sounds.html b/docs/sounds.html new file mode 100644 index 0000000..cc812af --- /dev/null +++ b/docs/sounds.html @@ -0,0 +1,56 @@ + + +Sound Preferences + + + + +

Preferences: Sounds

+ +

Sound Effects

+ +
+ Enable Sound Effects +
+ Check this to enable the use of sound effects globally. Disable this + to globally disable found effects.

+ + NOTE: To mute sound effects temporarily, just choose "Mute sounds" + from the "Chat" menu. +

+
+ +

Events

+ +
+ When a user joins the room... +
+ Specify a sound effect to play when a user enters the room. +
+ + When a user exits the room... +
+ Specify a sound effect to play when a user leaves the room + (or is disconnected). +
+ + When a message is received... +
+ Specify a sound effect to play when a user sends a public + message in the chat room. +
+ + When a private message is received... +
+ Specify a sound effect to play when a private message is + received. +
+
+ +

Adding Sound Effects

+ +To add your own sound effects, drop the sound file into the "sfx" folder, + located within the PCCC folder. + + + diff --git a/docs/transcript.html b/docs/transcript.html new file mode 100644 index 0000000..0ccd9de --- /dev/null +++ b/docs/transcript.html @@ -0,0 +1,32 @@ + + +Save Transcript + + + + +

Save Transcript

+ +You can save the current chat conversation by choosing "File" from the menu bar, + and clicking "Save Transcript". You can save it either to an HTML document or to + a plain text file. + +

HTML Document

+ +When saved to an HTML document, the chat dialog is written as compliant XHTML + source code, keeping all of the chat colors intact. + +

Text Document

+ +When saved to a text document, the chat dialog is saved in plain text format, + without any of the colors and special formatting. + +

Automatic Logging

+ +In the General tab of the Preferences window, there's + an option to automatically save all transcripts. To review the saved logs, look + in your profile directory. See User Profiles for more + information. + + + diff --git a/docs/usage.html b/docs/usage.html new file mode 100644 index 0000000..fba7d3a --- /dev/null +++ b/docs/usage.html @@ -0,0 +1,65 @@ + + +Using PCCC + + + + +

Getting Started with PCCC

+ +When you start up the Perl CyanChat Client, you will be presented with the + Main Window, where a message by "ChatClient" is + already visible on screen, welcoming you to PCCC.

+ +To connect to the CyanChat room, select "Connection" from the menu bar, + and choose "Connect". This will attempt to connect you to the CyanChat server. + Once connected, you will see a lot of "lobby messages" sent by + "ChatServer", and moments later the Who List will + display the list of users logged into the chat room (if there are any logged in). + +

Chat Presence

+ +To enter the chat, type in a nickname for yourself in the "Name:" box, and + then press the "Join Chat" button. Note that your nickname must be less than + 20 characters long, and cannot contain a caret (^), pipe symbol ("|"), or a + comma. If your name is accepted by the server, you will "link in" to the room. Otherwise, + ChatServer will tell you that your nick was invalid.

+ +When a user links in to the chat room (in other words, enters the room), a message + will display similar to the following: + +

+ \\\\\[Nick] <links in from somewhere on the internet Age>//// +
+ +When a user links out of the chat room (in other words, exiting the room by + clicking the "Exit Chat" button), a message will display similar to the following: + +
+ /////[Nick] <links safely back to their home Age>\\\\\ +
+ +Finally, when a user disconnects from the chat room, the following message + is displayed: + +
+ /////[Nick] <mistakenly used an unsafe linking book without a maintainer's suit + *ZZZZZWHAP*>\\\\\ +
+ +All users in the chat room also have a unique address assigned to them. This + is a mangled up form of their IP address, so multiple nicks from the same computer + will probably have the same address. To see a user's address, right-click their name + in the Who List. + +

Standard Messaging

+ +To send a message to the chat room, simply type it into the entry box at the top of the + Main Window and press the Return key. Note that you must be logged in before you can + send messages!

+ +See Private Messaging and Action Messages + for information about different types of messaging. + + + diff --git a/lib/Net/CyanChat.pm b/lib/Net/CyanChat.pm new file mode 100644 index 0000000..eb9d539 --- /dev/null +++ b/lib/Net/CyanChat.pm @@ -0,0 +1,696 @@ +package Net::CyanChat; + +use strict; +use warnings; +use IO::Socket; +use IO::Select; + +our $VERSION = '0.06'; + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + + my $self = { + host => 'cho.cyan.com', # Default CC Host + port => 1812, # Default CC Port (1813=debugging) + debug => 0, # Debug Mode + proto => 1, # Use Protocol 1 (not 0) + sock => undef, # Socket Object + select => undef, # Select Object + pinged => 0, # Last Ping Time + refresh => 60, # Ping Rate = 60 Seconds + nickname => '', # Our Nickname + handlers => {}, # Handlers + connected => 0, # Are We Connected? + accepted => 0, # Logged in? + who => {}, # Who List + ignored => {}, # Ignored List + nicks => {}, # Nickname Lookup Table + @_, + }; + + # Protocol support numbers: 0 and 1. + if ($self->{proto} < 0 || $self->{proto} > 1) { + die "Unsupported protocol version: must be 0 or 1!"; + } + + bless ($self,$class); + return $self; +} + +sub version { + my ($self) = @_; + return $VERSION; +} + +sub debug { + my ($self,$msg) = @_; + + return unless $self->{debug} == 1; + print "Net::CyanChat::debug // $msg\n"; +} + +sub send { + my ($self,$data) = @_; + + # Send the data. + if (defined $self->{sock}) { + $self->_event ('Packet', 'outgoing', $data); + + # Send true CrLf + $self->{sock}->send ("$data\x0d\x0a") or do { + # We've been disconnected! + $self->{sock}->close(); + $self->{sock} = undef; + $self->{select} = undef; + $self->{connected} = 0; + $self->{nick} = ''; + $self->{pinged} = 0; + $self->{who} = {}; + $self->{nicks} = {}; + $self->_event ('Disconnected'); + }; + } + else { + warn "Could not send \"$data\" to CyanChat: connection not established!"; + } +} + +sub setHandler { + my ($self,$event,$code) = @_; + + # Set this handler. + $self->{handlers}->{$event} = $code; +} + +sub connect { + my ($self) = @_; + + # Connect to CyanChat. + $self->{sock} = new IO::Socket::INET ( + PeerAddr => $self->{host}, + PeerPort => $self->{port}, + Proto => 'tcp', + ); + + # Error? + if (!defined $self->{sock}) { + $self->_event ('Error', "00|Connection Error", "Net::CyanChat Connection Error: $!"); + } + + # Create a select object. + $self->{select} = IO::Select->new ($self->{sock}); + + # Send that we're ready. + $self->send ("40|$self->{proto}"); +} + +sub start { + my ($self) = @_; + + while (1) { + $self->do_one_loop or last; + } +} + +sub login { + my ($self,$nick) = @_; + + if (length $nick > 0) { + # Sign in. + $self->send ("10|$nick"); + $self->{nickname} = $nick; + return 1; + } + + return 0; +} + +sub logout { + my ($self) = @_; + + return 0 unless length $self->{nickname} > 0; + $self->{nickname} = ''; + $self->{accepted} = 0; + $self->send ("15"); + return 1; +} + +sub sendMessage { + my ($self,$msg) = @_; + + # Send the message. + return 0 unless length $msg > 0; + $self->send ("30|^1$msg"); +} + +sub sendPrivate { + my ($self,$to,$msg) = @_; + + return unless (length $to > 0 && length $msg > 0); + # Get the user's full nick. + my $nick = $self->{nicks}->{$to}; + + # Send this user a message. + $self->send ("20|$nick|^1$msg"); +} + +sub getBuddies { + my ($self) = @_; + + # Return the buddylist. + return $self->{who}; +} + +sub getFullName { + my ($self,$who) = @_; + + # Return this user's full name. + return $self->{full}->{$who} or 0; +} + +sub getAddress { + my ($self,$who) = @_; + + # Return this user's address. + return $self->{who}->{$who} or 0; +} + +sub protocol { + my ($self) = @_; + return $self->{proto}; +} + +sub nick { + my ($self) = @_; + + return $self->{nickname}; +} + +sub ignore { + my ($self,$who) = @_; + + # Ignore this user. + return unless length $who > 0; + $self->{ignored}->{$who} = 1; + $self->send ("70|$who"); +} +sub unignore { + my ($self,$who) = @_; + + # Unignore this user. + return unless length $who > 0; + delete $self->{ignored}->{$who}; + $self->send ("70|$who"); +} + +sub authenticate { + my ($self,$password) = @_; + + # Authenticate with a CC password. + $self->send ("50|$password"); +} + +sub promote { + my ($self,$user) = @_; + + # Promote this user to Special Guest. + $self->send ("60|$user|4"); +} + +sub demote { + my ($self,$user) = @_; + + # Demote this user. + $self->send ("60|$user|0"); +} + +sub _event { + my ($self,$event,@data) = @_; + + return unless exists $self->{handlers}->{$event}; + + &{$self->{handlers}->{$event}} ($self,@data); +} + +sub do_one_loop { + my ($self) = @_; + + # Time to ping again? + if ($self->{pinged} > 0) { + # If connected... + if ($self->{connected} == 1) { + # If logged in... + if ($self->{accepted} == 1) { + # If refresh time has passed... + if (time() - $self->{pinged} >= $self->{refresh}) { + # To ping, send a private message to nobody. + $self->send ("20||^1ping"); + $self->{pinged} = time(); + } + } + } + } + + return unless defined $self->{select}; + + # Loop with the server. + my @ready = $self->{select}->can_read(.001); + return unless(@ready); + + foreach my $socket (@ready) { + my $resp; + $self->{sock}->recv ($resp,2048,0); + my @in = split(/\n/, $resp); + + # The server has sent us a message! + foreach my $said (@in) { + $said =~ s/\r//ig; + my ($command,@args) = split(/\|/, $said); + + # The first message received? + if ($self->{connected} == 0) { + $self->{connected} = 1; + $self->_event ('Connected'); + $self->{pinged} = time(); + } + + $self->_event ('Packet', 'incoming', $said); + + # Go through the commands. + if ($command == 10) { + # 10 = Name is invalid. + $self->_event ('Error', 10, "Your name is invalid."); + } + elsif ($command == 11) { + # 11 = Name accepted. + $self->{accepted} = 1; + $self->_event ('Name_Accepted'); + } + elsif ($command == 21) { + # 21 = Private Message + my $type = 0; + my ($level) = $args[0] =~ /^(\d)/; + $type = $args[1] =~ /^\^(\d)/; + $args[0] =~ s/^(\d)//ig; + $args[1] =~ s/^\^(\d)//ig; + + # Get the sender's nick and address. + my ($nick,$addr) = split(/\,/, $args[0], 2); + + # Skip ignored users. + next if exists $self->{ignored}->{$nick}; + + shift (@args); + my $text = join ('|',@args); + + # Call the event. + $self->_event ('Private', $nick, $level, $addr, $text); + } + elsif ($command == 31) { + # 31 = Public Message. + my $type = 1; + my ($level) = $args[0] =~ /^(\d)/; + ($type) = $args[1] =~ /^\^(\d)/; + $args[0] =~ s/^(\d)//i; + $args[1] =~ s/^\^(\d)//i; + + # Get the sender's nick and address. + my ($nick,$addr) = split(/\,/, $args[0], 2); + + # Skip ignored users. + next if exists $self->{ignored}->{$nick}; + + # Chop off spaces. + $args[1] =~ s/^\s//ig; + + # Shift off data. + shift (@args); # nickname + my $text = join ('|',@args); + + # User has entered the room. + if ($type == 2) { + # Call the event. + $self->_event ('Chat_Buddy_In', $nick, $level, $addr, $text); + } + elsif ($type == 3) { + # Call the event. + $self->_event ('Chat_Buddy_Out', $nick, $level, $addr, $text); + } + else { + # Normal message. + $self->_event ('Message', $nick, $level, $addr, $text); + } + } + elsif ($command == 35) { + # 35 = Who List Update. + my %this = (); + foreach my $user (@args) { + my ($nick,$addr) = split(/\,/, $user, 2); + my $fullNick = $nick; + + # Get data about this user. + my ($level) = $nick =~ /^(\d)/; + $nick =~ s/^(\d)//i; + + # User is online. + $self->{who}->{$nick} = $addr; + $this{$nick} = 1; + + # Call the event. + $self->{nicks}->{$nick} = $fullNick; + $self->_event ('Chat_Buddy_Here', $nick, $level, $addr); + } + + # New event: WhoList = sends the entire Who List at once. + $self->_event ('WhoList', @args); + + # See if anybody should be dropped. + foreach my $who (keys %{$self->{who}}) { + if (!exists $this{$who}) { + # Buddy's gone. + delete $self->{who}->{$who}; + } + } + } + elsif ($command == 40) { + # 40 = Server welcome message (the "pong" of 40 from the client). + $self->_event ('Welcome', $args[0]); + } + elsif ($command == 70) { + # 70 = Ignored/Unignored a user. + my $user = $args[0]; + if (exists $self->{ignored}->{$user}) { + delete $self->{ignored}->{$user}; + $self->_event ('Ignored', 0, $user); + } + else { + $self->{ignored}->{$user} = 1; + $self->_event ('Ignored', 1, $user); + } + } + else { + $self->debug ("Unknown event code from server: $command|" + . join ('|', @args) ); + } + } + } + + return 1; +} + +1; +__END__ + +=head1 NAME + +Net::CyanChat - Perl interface for connecting to Cyan Worlds' chat room. + +=head1 SYNOPSIS + + use Net::CyanChat; + + my $cyan = new Net::CyanChat ( + host => 'cho.cyan.com', # default + port => 1812, # main port--1813 is for testing + proto => 1, # use protocol 1.0 + refresh => 60, # ping rate (default) + ); + + # Set up handlers. + $cyan->setHandler (foo => \&bar); + + # Connect + $cyan->start(); + +=head1 DESCRIPTION + +Net::CyanChat is a Perl module for object-oriented connections to Cyan Worlds, Inc.'s +chat room. + +=head1 NOTE TO DEVELOPERS + +Cyan Chat regulars really HATE bots! Recommended usage of this module is for developing +your own client, or a silent logging bot. Auto-Shorah (greeting users who enter the room) +is strongly advised against. + +=head1 METHODS + +=head2 new (ARGUMENTS) + +Constructor for a new CyanChat object. Pass in any arguments you need. Some standard arguments +are: host (defaults to cho.cyan.com), port (defaults to 1812), proto (protocol version--0 or 1--defaults +to 1), debug, or refresh. + +Returns a CyanChat object. + +=head2 version + +Returns the version number. + +=head2 debug (MESSAGE) + +Called by the module itself for debug messages. + +=head2 send (DATA) + +Send raw data to the CyanChat server. + +=head2 setHandler (EVENT_CODE => CODEREF) + +Set up a handler for the CyanChat connection. See below for a list of handlers. + +=head2 connect + +Connect to CyanChat's server. + +=head2 start + +Start a loop of do_one_loop's. + +=head2 do_one_loop + +Perform a single loop on the server. + +=head2 login (NICK) + +After receiving a "Connected" event from the server, it is okay to log in now. NICK +should be no more than 20 characters and cannot contain a pipe symbol "|". + +This method can be called even after you have logged in once, for example if you want +to change your nickname without logging out and then back in. + +=head2 logout + +Log out of CyanChat. Must be logged in first. + +=head2 sendMessage (MESSAGE) + +Broadcast a message publicly to the chat room. Can only be called after you have logged +in through $cyan->login. + +=head2 sendPrivate (TO, MESSAGE) + +Send a private message to recipient TO. Must be logged in first. + +=head2 getBuddies + +Returns a hashref containing each buddy's username as the keys and their addresses as the values. + +=head2 getFullName (NICK) + +Returns the full name of passed in NICK. If NICK is not in the room, returns 0. FullName is the +name that CyanChat recognizes NICK by (including their auth code, i.e. "0username" for normal +users and "1username" for Cyan staff). + +=head2 getAddress (NICK) + +Returns the address to NICK. This is not their IP address; CyanChat encrypts their IP into this +address, and it is basicly a unique identifier for a connection. Multiple users logged on from the +same IP address will have the same chat address. Ignoring users will ignore them by address. + +=head2 protocol + +Returns the protocol version you are using. Will return 0 or 1. + +=head2 ignore (USER), unignore (USER) + +Ignore and unignore a username. When a user is ignored, the Message and Private events will not +be called when they send a message. + +=head2 nick + +Returns the currently signed in nickname of the CyanChat object. + +=head1 ADVANCED METHODS + +B These methods are very dangerous to use if you don't know what you're doing. +Don't call authenticate() unless you know for sure what the CyanChat admin password is, +and don't call promote() or demote() unless you are already authenticated as a CyanChat +staff user. + +Calling the authenticate() command with the wrong password will most likely get you +banned from CyanChat, and calling promote() or demote() without being an admin user +will probably have the same effect. + +In other words, B + +=head2 authenticate (PASSWORD) + +Authenticate your connection as a Cyan Worlds staff member. Call this method before +entering the chat room. + +=head2 promote (USER) + +Promote USER to a Special Guest. + +=head2 demote (USER) + +Demote USER to a normal user level. + +=head1 HANDLERS + +=head2 Connected (CYANCHAT) + +Called when a connection has been established, and the server recognizes your client's +presence. At this point, you can call CYANCHAT->login (NICK) to log into the chat room. + +=head2 Disconnected (CYANCHAT) + +Called when a disconnect has been detected. + +=head2 Welcome (CYANCHAT, MESSAGE) + +Called after the server recognizes your client (almost simultaneously to Connected). +MESSAGE are messages that the CyanChat server sends--mostly just includes a list of the +chat room's rules. + +=head2 Message (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a user sends a message publicly in chat. NICK is their nickname, LEVEL is their +auth level (0 = normal, 1 = Cyan employee, etc. - see below for full list). ADDRESS is their +chat address, and MESSAGE is their message. + +=head2 Private (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a user sends a private message to your client. All the arguments are the same +as the Message handler. + +=head2 Ignored (CYANCHAT, IGNORE, NICK) + +Called when a user has been ignored or unignored. IGNORE will be 1 (ignoring) or +0 (unignoring). NICK is their nickname. + +=head2 Chat_Buddy_In (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a buddy enters the chat room. NICK, LEVEL, and ADDRESS are the same as in the +Message and Private handlers. MESSAGE is their join message (i.e. "") + +=head2 Chat_Buddy_Out (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a buddy exits. MESSAGE is their exit message (i.e. "" +for normal log out, or "" for +disconnected). + +=head2 Chat_Buddy_Here (CYANCHAT, NICK, LEVEL, ADDRESS) + +Called for each member currently in the room. Each time the Who List updates, this handler is called +for each buddy in the room. + +=head2 WhoList (CYANCHAT, USERS) + +This handler is called whenever a "35" (WhoList) event is received from the server. USERS is an array +of the raw user data the server sent. The array is full of elements of the format: + + #username,address + +Where # is the auth level. Unlike Chat_Buddy_Here, your program needs to loop and parse out info +from each of the users. + +=head2 Name_Accepted (CYANCHAT) + +The CyanChat server has accepted your name. + +=head2 Error (CYANCHAT, CODE, STRING) + +Handles errors issued by CyanChat. CODE is the exact server code issued that caused the error. +STRING is either an English description or the exact text the server sent. + +=head1 CYAN CHAT RULES + +The CyanChat server strictly enforces these rules: + + Be respectful and sensitive to others (please, no platform wars). + Keep it "G" rated (family viewing), both in language and content. + And HAVE FUN! + + Termination of use can happen without warning! + +=head1 CYAN CHAT AUTH LEVELS + +Auth levels (received as LEVEL to most handlers, or prefixed onto a user's FullName) are as follows: + + 0 is for regular chat user (should be in white) + 1 is for Cyan Worlds employee (should be in cyan) + 2 is for CyanChat Server message (should be in green) + 4 is for special guest (should be in gold) + Any other number is probably a client error message (and is in red) + +=head1 CHANGE LOG + +Version 0.05 + + - Fixed the end-of-line characters, it now sends a true CrLf. + - Added the WhoList handler. + - Added the authenticate(), promote(), and demote() methods. + +Version 0.04 + + - The enter/exit chat messages now go by the tag number (like it's supposed to), + not by the contained text. + - Messages can contain pipes in them and be read okay through the module. + - Added a "ping" function. Apparently Cho will disconnect clients who don't do + anything in 5 minutes. The "ping" function also helps detect disconnects! + - The Disconnected handler has been added to detect disconnects. + +Version 0.03 + + - Bug fix: the $level received to most handlers used to be 1 (cyan staff) even + though it should've been 0 (or any other number), so this has been fixed. + +Version 0.01 + + - Initial release. + - Fully supports both protocols 0 and 1 of CyanChat. + +=head1 SEE ALSO + +Net::CyanChat::Server + +CyanChat Protocol Documentation: http://cho.cyan.com/chat/programmers.html + +=head1 AUTHOR + +Cerone J. Kirsle + +=head1 COPYRIGHT AND LICENSE + + Net::CyanChat - Perl interface to CyanChat. + Copyright (C) 2005 Cerone J. Kirsle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +=cut diff --git a/lib/Net/CyanChat.pm~ b/lib/Net/CyanChat.pm~ new file mode 100644 index 0000000..2b9f0d9 --- /dev/null +++ b/lib/Net/CyanChat.pm~ @@ -0,0 +1,700 @@ +package Net::CyanChat; + +use strict; +use warnings; +use IO::Socket; +use IO::Select; + +our $VERSION = '0.06'; + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + + my $self = { + host => 'cho.cyan.com', # Default CC Host + port => 1812, # Default CC Port (1813=debugging) + debug => 0, # Debug Mode + proto => 1, # Use Protocol 1 (not 0) + sock => undef, # Socket Object + select => undef, # Select Object + pinged => 0, # Last Ping Time + refresh => 60, # Ping Rate = 60 Seconds + nickname => '', # Our Nickname + handlers => {}, # Handlers + connected => 0, # Are We Connected? + accepted => 0, # Logged in? + who => {}, # Who List + ignored => {}, # Ignored List + nicks => {}, # Nickname Lookup Table + @_, + }; + + # Protocol support numbers: 0 and 1. + if ($self->{proto} < 0 || $self->{proto} > 1) { + die "Unsupported protocol version: must be 0 or 1!"; + } + + bless ($self,$class); + return $self; +} + +sub version { + my ($self) = @_; + return $VERSION; +} + +sub debug { + my ($self,$msg) = @_; + + return unless $self->{debug} == 1; + print "Net::CyanChat::debug // $msg\n"; +} + +sub send { + my ($self,$data) = @_; + + # Send the data. + if (defined $self->{sock}) { + $self->_event ('Packet', 'outgoing', $data); + + # Send true CrLf + $self->{sock}->send ("$data\x0d\x0a") or do { + # We've been disconnected! + $self->{sock}->close(); + $self->{sock} = undef; + $self->{select} = undef; + $self->{connected} = 0; + $self->{nick} = ''; + $self->{pinged} = 0; + $self->{who} = {}; + $self->{nicks} = {}; + $self->_event ('Disconnected'); + }; + } + else { + warn "Could not send \"$data\" to CyanChat: connection not established!"; + } +} + +sub setHandler { + my ($self,$event,$code) = @_; + + # Set this handler. + $self->{handlers}->{$event} = $code; +} + +sub connect { + my ($self) = @_; + + # Connect to CyanChat. + $self->{sock} = new IO::Socket::INET ( + PeerAddr => $self->{host}, + PeerPort => $self->{port}, + Proto => 'tcp', + ); + + # Error? + if (!defined $self->{sock}) { + $self->_event ('Error', "00|Connection Error", "Net::CyanChat Connection Error: $!"); + } + + # Create a select object. + $self->{select} = IO::Select->new ($self->{sock}); + + # Send that we're ready. + $self->send ("40|$self->{proto}"); +} + +sub start { + my ($self) = @_; + + while (1) { + $self->do_one_loop or last; + } +} + +sub login { + my ($self,$nick) = @_; + + if (length $nick > 0) { + # Sign in. + $self->send ("10|$nick"); + $self->{nickname} = $nick; + return 1; + } + + return 0; +} + +sub logout { + my ($self) = @_; + + return 0 unless length $self->{nickname} > 0; + $self->{nickname} = ''; + $self->{accepted} = 0; + $self->send ("15"); + return 1; +} + +sub sendMessage { + my ($self,$msg) = @_; + + # Send the message. + return 0 unless length $msg > 0; + $self->send ("30|^1$msg"); +} + +sub sendPrivate { + my ($self,$to,$msg) = @_; + + return unless (length $to > 0 && length $msg > 0); + # Get the user's full nick. + my $nick = $self->{nicks}->{$to}; + + # Send this user a message. + $self->send ("20|$nick|^1$msg"); +} + +sub getBuddies { + my ($self) = @_; + + # Return the buddylist. + return $self->{who}; +} + +sub getFullName { + my ($self,$who) = @_; + + # Return this user's full name. + return $self->{full}->{$who} or 0; +} + +sub getAddress { + my ($self,$who) = @_; + + # Return this user's address. + return $self->{who}->{$who} or 0; +} + +sub protocol { + my ($self) = @_; + return $self->{proto}; +} + +sub nick { + my ($self) = @_; + + return $self->{nickname}; +} + +sub ignore { + my ($self,$who) = @_; + + # Ignore this user. + return unless length $who > 0; + $self->{ignored}->{$who} = 1; + $self->send ("70|$who"); +} +sub unignore { + my ($self,$who) = @_; + + # Unignore this user. + return unless length $who > 0; + delete $self->{ignored}->{$who}; + $self->send ("70|$who"); +} + +sub authenticate { + my ($self,$password) = @_; + + # Authenticate with a CC password. + $self->send ("50|$password"); +} + +sub promote { + my ($self,$user) = @_; + + # Promote this user to Special Guest. + $self->send ("60|$user|4"); +} + +sub demote { + my ($self,$user) = @_; + + # Demote this user. + $self->send ("60|$user|0"); +} + +sub _event { + my ($self,$event,@data) = @_; + + print "_event: $event, @data\n"; + + return unless exists $self->{handlers}->{$event}; + + print "calling event for $event\n"; + &{$self->{handlers}->{$event}} ($self,@data); +} + +sub do_one_loop { + my ($self) = @_; + + # Time to ping again? + if ($self->{pinged} > 0) { + # If connected... + if ($self->{connected} == 1) { + # If logged in... + if ($self->{accepted} == 1) { + # If refresh time has passed... + if (time() - $self->{pinged} >= $self->{refresh}) { + # To ping, send a private message to nobody. + $self->send ("20||^1ping"); + $self->{pinged} = time(); + } + } + } + } + + return unless defined $self->{select}; + + # Loop with the server. + my @ready = $self->{select}->can_read(.001); + return unless(@ready); + + foreach my $socket (@ready) { + my $resp; + $self->{sock}->recv ($resp,2048,0); + my @in = split(/\n/, $resp); + + # The server has sent us a message! + foreach my $said (@in) { + $said =~ s/\r//ig; + my ($command,@args) = split(/\|/, $said); + + # The first message received? + if ($self->{connected} == 0) { + $self->{connected} = 1; + $self->_event ('Connected'); + $self->{pinged} = time(); + } + + $self->_event ('Packet', 'incoming', $said); + + # Go through the commands. + if ($command == 10) { + # 10 = Name is invalid. + $self->_event ('Error', 10, "Your name is invalid."); + } + elsif ($command == 11) { + # 11 = Name accepted. + $self->{accepted} = 1; + $self->_event ('Name_Accepted'); + } + elsif ($command == 21) { + # 21 = Private Message + my $type = 0; + my ($level) = $args[0] =~ /^(\d)/; + $type = $args[1] =~ /^\^(\d)/; + $args[0] =~ s/^(\d)//ig; + $args[1] =~ s/^\^(\d)//ig; + + # Get the sender's nick and address. + my ($nick,$addr) = split(/\,/, $args[0], 2); + + # Skip ignored users. + next if exists $self->{ignored}->{$nick}; + + shift (@args); + my $text = join ('|',@args); + + # Call the event. + $self->_event ('Private', $nick, $level, $addr, $text); + } + elsif ($command == 31) { + # 31 = Public Message. + my $type = 1; + my ($level) = $args[0] =~ /^(\d)/; + ($type) = $args[1] =~ /^\^(\d)/; + $args[0] =~ s/^(\d)//i; + $args[1] =~ s/^\^(\d)//i; + + # Get the sender's nick and address. + my ($nick,$addr) = split(/\,/, $args[0], 2); + + # Skip ignored users. + next if exists $self->{ignored}->{$nick}; + + # Chop off spaces. + $args[1] =~ s/^\s//ig; + + # Shift off data. + shift (@args); # nickname + my $text = join ('|',@args); + + # User has entered the room. + if ($type == 2) { + # Call the event. + $self->_event ('Chat_Buddy_In', $nick, $level, $addr, $text); + } + elsif ($type == 3) { + # Call the event. + $self->_event ('Chat_Buddy_Out', $nick, $level, $addr, $text); + } + else { + # Normal message. + $self->_event ('Message', $nick, $level, $addr, $text); + } + } + elsif ($command == 35) { + # 35 = Who List Update. + my %this = (); + foreach my $user (@args) { + my ($nick,$addr) = split(/\,/, $user, 2); + my $fullNick = $nick; + + # Get data about this user. + my ($level) = $nick =~ /^(\d)/; + $nick =~ s/^(\d)//i; + + # User is online. + $self->{who}->{$nick} = $addr; + $this{$nick} = 1; + + # Call the event. + $self->{nicks}->{$nick} = $fullNick; + $self->_event ('Chat_Buddy_Here', $nick, $level, $addr); + } + + # New event: WhoList = sends the entire Who List at once. + $self->_event ('WhoList', @args); + + # See if anybody should be dropped. + foreach my $who (keys %{$self->{who}}) { + if (!exists $this{$who}) { + # Buddy's gone. + delete $self->{who}->{$who}; + } + } + } + elsif ($command == 40) { + print "^^^ got 40 ($args[0])\n"; + # 40 = Server welcome message (the "pong" of 40 from the client). + $self->_event ('Welcome', $args[0]); + } + elsif ($command == 70) { + # 70 = Ignored/Unignored a user. + my $user = $args[0]; + if (exists $self->{ignored}->{$user}) { + delete $self->{ignored}->{$user}; + $self->_event ('Ignored', 0, $user); + } + else { + $self->{ignored}->{$user} = 1; + $self->_event ('Ignored', 1, $user); + } + } + else { + $self->debug ("Unknown event code from server: $command|" + . join ('|', @args) ); + } + } + } + + return 1; +} + +1; +__END__ + +=head1 NAME + +Net::CyanChat - Perl interface for connecting to Cyan Worlds' chat room. + +=head1 SYNOPSIS + + use Net::CyanChat; + + my $cyan = new Net::CyanChat ( + host => 'cho.cyan.com', # default + port => 1812, # main port--1813 is for testing + proto => 1, # use protocol 1.0 + refresh => 60, # ping rate (default) + ); + + # Set up handlers. + $cyan->setHandler (foo => \&bar); + + # Connect + $cyan->start(); + +=head1 DESCRIPTION + +Net::CyanChat is a Perl module for object-oriented connections to Cyan Worlds, Inc.'s +chat room. + +=head1 NOTE TO DEVELOPERS + +Cyan Chat regulars really HATE bots! Recommended usage of this module is for developing +your own client, or a silent logging bot. Auto-Shorah (greeting users who enter the room) +is strongly advised against. + +=head1 METHODS + +=head2 new (ARGUMENTS) + +Constructor for a new CyanChat object. Pass in any arguments you need. Some standard arguments +are: host (defaults to cho.cyan.com), port (defaults to 1812), proto (protocol version--0 or 1--defaults +to 1), debug, or refresh. + +Returns a CyanChat object. + +=head2 version + +Returns the version number. + +=head2 debug (MESSAGE) + +Called by the module itself for debug messages. + +=head2 send (DATA) + +Send raw data to the CyanChat server. + +=head2 setHandler (EVENT_CODE => CODEREF) + +Set up a handler for the CyanChat connection. See below for a list of handlers. + +=head2 connect + +Connect to CyanChat's server. + +=head2 start + +Start a loop of do_one_loop's. + +=head2 do_one_loop + +Perform a single loop on the server. + +=head2 login (NICK) + +After receiving a "Connected" event from the server, it is okay to log in now. NICK +should be no more than 20 characters and cannot contain a pipe symbol "|". + +This method can be called even after you have logged in once, for example if you want +to change your nickname without logging out and then back in. + +=head2 logout + +Log out of CyanChat. Must be logged in first. + +=head2 sendMessage (MESSAGE) + +Broadcast a message publicly to the chat room. Can only be called after you have logged +in through $cyan->login. + +=head2 sendPrivate (TO, MESSAGE) + +Send a private message to recipient TO. Must be logged in first. + +=head2 getBuddies + +Returns a hashref containing each buddy's username as the keys and their addresses as the values. + +=head2 getFullName (NICK) + +Returns the full name of passed in NICK. If NICK is not in the room, returns 0. FullName is the +name that CyanChat recognizes NICK by (including their auth code, i.e. "0username" for normal +users and "1username" for Cyan staff). + +=head2 getAddress (NICK) + +Returns the address to NICK. This is not their IP address; CyanChat encrypts their IP into this +address, and it is basicly a unique identifier for a connection. Multiple users logged on from the +same IP address will have the same chat address. Ignoring users will ignore them by address. + +=head2 protocol + +Returns the protocol version you are using. Will return 0 or 1. + +=head2 ignore (USER), unignore (USER) + +Ignore and unignore a username. When a user is ignored, the Message and Private events will not +be called when they send a message. + +=head2 nick + +Returns the currently signed in nickname of the CyanChat object. + +=head1 ADVANCED METHODS + +B These methods are very dangerous to use if you don't know what you're doing. +Don't call authenticate() unless you know for sure what the CyanChat admin password is, +and don't call promote() or demote() unless you are already authenticated as a CyanChat +staff user. + +Calling the authenticate() command with the wrong password will most likely get you +banned from CyanChat, and calling promote() or demote() without being an admin user +will probably have the same effect. + +In other words, B + +=head2 authenticate (PASSWORD) + +Authenticate your connection as a Cyan Worlds staff member. Call this method before +entering the chat room. + +=head2 promote (USER) + +Promote USER to a Special Guest. + +=head2 demote (USER) + +Demote USER to a normal user level. + +=head1 HANDLERS + +=head2 Connected (CYANCHAT) + +Called when a connection has been established, and the server recognizes your client's +presence. At this point, you can call CYANCHAT->login (NICK) to log into the chat room. + +=head2 Disconnected (CYANCHAT) + +Called when a disconnect has been detected. + +=head2 Welcome (CYANCHAT, MESSAGE) + +Called after the server recognizes your client (almost simultaneously to Connected). +MESSAGE are messages that the CyanChat server sends--mostly just includes a list of the +chat room's rules. + +=head2 Message (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a user sends a message publicly in chat. NICK is their nickname, LEVEL is their +auth level (0 = normal, 1 = Cyan employee, etc. - see below for full list). ADDRESS is their +chat address, and MESSAGE is their message. + +=head2 Private (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a user sends a private message to your client. All the arguments are the same +as the Message handler. + +=head2 Ignored (CYANCHAT, IGNORE, NICK) + +Called when a user has been ignored or unignored. IGNORE will be 1 (ignoring) or +0 (unignoring). NICK is their nickname. + +=head2 Chat_Buddy_In (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a buddy enters the chat room. NICK, LEVEL, and ADDRESS are the same as in the +Message and Private handlers. MESSAGE is their join message (i.e. "") + +=head2 Chat_Buddy_Out (CYANCHAT, NICK, LEVEL, ADDRESS, MESSAGE) + +Called when a buddy exits. MESSAGE is their exit message (i.e. "" +for normal log out, or "" for +disconnected). + +=head2 Chat_Buddy_Here (CYANCHAT, NICK, LEVEL, ADDRESS) + +Called for each member currently in the room. Each time the Who List updates, this handler is called +for each buddy in the room. + +=head2 WhoList (CYANCHAT, USERS) + +This handler is called whenever a "35" (WhoList) event is received from the server. USERS is an array +of the raw user data the server sent. The array is full of elements of the format: + + #username,address + +Where # is the auth level. Unlike Chat_Buddy_Here, your program needs to loop and parse out info +from each of the users. + +=head2 Name_Accepted (CYANCHAT) + +The CyanChat server has accepted your name. + +=head2 Error (CYANCHAT, CODE, STRING) + +Handles errors issued by CyanChat. CODE is the exact server code issued that caused the error. +STRING is either an English description or the exact text the server sent. + +=head1 CYAN CHAT RULES + +The CyanChat server strictly enforces these rules: + + Be respectful and sensitive to others (please, no platform wars). + Keep it "G" rated (family viewing), both in language and content. + And HAVE FUN! + + Termination of use can happen without warning! + +=head1 CYAN CHAT AUTH LEVELS + +Auth levels (received as LEVEL to most handlers, or prefixed onto a user's FullName) are as follows: + + 0 is for regular chat user (should be in white) + 1 is for Cyan Worlds employee (should be in cyan) + 2 is for CyanChat Server message (should be in green) + 4 is for special guest (should be in gold) + Any other number is probably a client error message (and is in red) + +=head1 CHANGE LOG + +Version 0.05 + + - Fixed the end-of-line characters, it now sends a true CrLf. + - Added the WhoList handler. + - Added the authenticate(), promote(), and demote() methods. + +Version 0.04 + + - The enter/exit chat messages now go by the tag number (like it's supposed to), + not by the contained text. + - Messages can contain pipes in them and be read okay through the module. + - Added a "ping" function. Apparently Cho will disconnect clients who don't do + anything in 5 minutes. The "ping" function also helps detect disconnects! + - The Disconnected handler has been added to detect disconnects. + +Version 0.03 + + - Bug fix: the $level received to most handlers used to be 1 (cyan staff) even + though it should've been 0 (or any other number), so this has been fixed. + +Version 0.01 + + - Initial release. + - Fully supports both protocols 0 and 1 of CyanChat. + +=head1 SEE ALSO + +Net::CyanChat::Server + +CyanChat Protocol Documentation: http://cho.cyan.com/chat/programmers.html + +=head1 AUTHOR + +Cerone J. Kirsle + +=head1 COPYRIGHT AND LICENSE + + Net::CyanChat - Perl interface to CyanChat. + Copyright (C) 2005 Cerone J. Kirsle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +=cut diff --git a/lib/Tk/HyperText.pm b/lib/Tk/HyperText.pm new file mode 100644 index 0000000..bec51f1 --- /dev/null +++ b/lib/Tk/HyperText.pm @@ -0,0 +1,792 @@ +package Tk::HyperText; + +use strict; +use warnings; +use base qw(Tk::Derived Tk::ROText); +use Data::Dumper; + +our $VERSION = "0.03"; + +Construct Tk::Widget 'HyperText'; + +sub Populate { + my ($cw,$args) = @_; + + # Strip out the arguments we want before passing them to ROText. + my $opts = { + # -autorender => re-render the entire HTML document on update + # (otherwise, only render incoming HTML) + rerender => delete $args->{'-rerender'} || 1, + # -linkcommand => a callback when a user clicks a link + linkcommand => delete $args->{'-linkcommand'} || sub {}, + # -titlecommand => a callback when a page sets its title + titlecommand => delete $args->{'-titlecommand'} || sub {}, + # -attributes => define default attributes for each tag + attributes => { + body => { + bgcolor => '#FFFFFF', + text => '#000000', + link => '#0000FF', + vlink => '#990099', + alink => '#FF0000', + }, + font => { + family => 'Times New Roman', + size => 3, # HTML size; not point size. + color => '', # inherit from body + back => '', # inherit from body + }, + }, + }; + + # Copy attributes over. + if (exists $args->{'-attributes'}) { + my $attr = delete $args->{'-attributes'}; + foreach my $tag (keys %{$attr}) { + foreach my $name (keys %{$attr->{$tag}}) { + $opts->{attributes}->{$tag}->{$name} = $attr->{$tag}->{$name}; + } + } + } + + # Pass the remaining arguments to our ROText parent. + $args->{'-foreground'} = $opts->{attributes}->{body}->{text}; + $args->{'-background'} = $opts->{attributes}->{body}->{bgcolor}; + $cw->SUPER::Populate($args); + + # Reconfigure the ROText widget with our attributes. + $cw->SUPER::configure ( + -font => [ + -family => $opts->{attributes}->{font}->{family}, + -size => $cw->_size ($opts->{attributes}->{font}->{size}), + ], + ); + + $cw->{hypertext} = { + html => '', # holds HTML code + rerender => $opts->{rerender}, + attributes => $opts->{attributes}, + linkcommand => $opts->{linkcommand}, + titlecommand => $opts->{titlecommand}, + }; + +} + +sub insert { + my $cw = shift; + my $pos = shift; + $pos = $cw->index ($pos); + my $text = shift; + + # TODO: insert will only insert to the "end" + $cw->{hypertext}->{html} .= $text; + + + # If we're doing re-rendering, render the entire block of HTML at once. + if ($cw->{hypertext}->{rerender}) { + # Reset the title to blank. + &{$cw->{hypertext}->{titlecommand}} ($cw,""); + + # Render the whole entire page. + $cw->SUPER::delete ("0.0","end"); + $cw->render ($cw->{hypertext}->{html}); + } + else { + # Just render this text. + $cw->render ($text); + } +} + +sub delete { + my $cw = shift; + + # TODO: delete just deletes everything + $cw->{hypertext}->{html} = ''; + $cw->SUPER::delete ("0.0","end"); +} + +sub get { + my $cw = shift; + + # TODO: get just gets everything. + return $cw->{hypertext}->{html}; +} + +sub clear { + my $cw = shift; + + # Delete everything. + $cw->{hypertext}->{html} = ''; + $cw->SUPER::delete ("0.0","end"); +} + +sub render { + my ($cw,$html) = @_; + + # Make the HTML tags easier to find. + $html =~ s//%TK::HYPERTEXT::END::TAG%/g; + + # Split the tags apart. + my @parts = split(/%TK::HYPERTEXT/, $html); + + # Make an array of default styles for this render. + my %default = ( + bgcolor => $cw->{hypertext}->{body}->{bgcolor} || '#FFFFFF', + text => $cw->{hypertext}->{body}->{text} || '#000000', + link => $cw->{hypertext}->{body}->{link} || '#0000FF', + vlink => $cw->{hypertext}->{body}->{vlink} || '#990099', + alink => $cw->{hypertext}->{body}->{alink} || '#FF0000', + size => $cw->{hypertext}->{font}->{size} || 3, + font => $cw->{hypertext}->{font}->{family} || 'Times New Roman', + ); + + # Make an array of escape sequences. + my @escape = ( + '<' => '<', + '>' => '>', + '"' => '"', + ''' => "'", + ' ' => ' ', + '®' => chr(0x00ae), # registered trademark + '©' => chr(0x00a9), # copyright sign + '&' => '&', + ); + + # Reset the configuration of our ROText widget. + $cw->SUPER::configure ( + -background => $default{bgcolor}, + -foreground => $default{text}, + -font => [ + -family => $default{font}, + -size => $cw->_size ($default{size}), + ], + ); + + # Make an array of current styles for this render. + my %style = ( + weight => 'normal', # or 'bold' + slant => 'roman', # or 'italic' + underline => 0, # or 1 + overstrike => 0, # or 1 + family => '', + size => '', + foreground => '', + background => '', + justify => 'left', # or 'center' or 'right' + offset => 0, # changes for and + margin => 0, # for

s + titling => 0, # special--for title tags + title => '', # our page title + hyperlink => 0, # special--for hyperlinking + linktag => 0, # for hyperlinking + pre => 0, # special--for
formatted text
+	);
+
+	# Stack the styles up.
+	my @stackFont   = ();
+	my @stackColor  = ();
+	my @stackBG     = ();
+	my @stackSize   = ();
+	my @stackAlign  = ();
+	my @stackOffset = ();
+	my @stackMargin = ();
+	my @stackLinks  = ();
+
+	# Set this to 1 when the first line of actual text has been written.
+	# Blocklevel elements like to know.
+	my $lineWritten = 0;
+
+	# Keep an array of hyperlinks.
+	my %hyperlinks = ();
+
+	# Start parsing through the HTML code.
+	foreach my $sector (@parts) {
+		# Is this a tag we're in?
+		if ($sector =~ /^::START::TAG%/i) {
+			$sector =~ s/^::START::TAG%//; # strip it
+
+			# Find out the name of this tag and its attributes.
+			my ($name,$attr) = split(/\s+/, $sector, 2);
+			$name = uc($name);
+
+			next unless defined $name && length $name;
+
+			# Handle the various types of tags.
+			if ($name eq "HTML" || $name eq "/HTML") { # , 
+				# That was nice of the programmer.
+			}
+			elsif ($name eq "HEAD" || $name eq "/HEAD") { # , 
+				# We don't need to do anything with this, either.
+			}
+			elsif ($name eq "TITLE") { # 
+				# They're about to tell us the title.
+				$style{titling} = 1;
+			}
+			elsif ($name eq "/TITLE") { # 
+				# Stop titling our page.
+				$style{titling} = 0;
+
+				# Call our title-setting callback.
+				&{$cw->{hypertext}->{titlecommand}} ($cw,$style{title});
+			}
+			elsif ($name eq "BODY") { # 
+				# Collect as much data as we can.
+				next unless defined $attr;
+				if ($attr =~ /bgcolor="(.+?)"/i) {
+					$cw->SUPER::configure (-background => $1);
+					$default{bgcolor} = $1;
+				}
+				if ($attr =~ /link="(.+?)"/i) {
+					$default{link} = $1;
+				}
+				if ($attr =~ /vlink="(.+?)"/i) {
+					$default{vlink} = $1;
+				}
+				if ($attr =~ /alink="(.+?)"/i) {
+					$default{alink} = $1;
+				}
+				if ($attr =~ /text="(.+?)"/i) {
+					$cw->SUPER::configure (-foreground => $1);
+					$default{text} = $1;
+				}
+			}
+			elsif ($name eq "/BODY") { # 
+				# Technically we shouldn't allow anymore HTML at this point,
+				# on account of the , but let's not be too picky.
+			}
+			elsif ($name eq "BASEFONT") { # 
+				# Collect as much data as we can.
+				if ($attr =~ /face="(.+?)"/i) {
+					$default{font} = $1;
+				}
+				if ($attr =~ /size="(.+?)"/i) {
+					$default{size} = $1;
+				}
+				if ($attr =~ /color="(.+?)"/i) {
+					$default{text} = $1;
+				}
+			}
+			elsif ($name eq "FONT") { # 
+				# Collect info.
+				if ($attr =~ /face="(.+?)"/i) {
+					push (@stackFont,$1);
+					$style{family} = $1;
+				}
+				if ($attr =~ /color="(.+?)"/i) {
+					push (@stackColor,$1);
+					$style{foreground} = $1;
+				}
+				if ($attr =~ /back="(.+?)"/i) {
+					push (@stackBG,$1);
+					$style{background} = $1;
+				}
+				if ($attr =~ /size="(.+?)"/i) {
+					push (@stackSize,$1);
+					$style{size} = $1;
+				}
+			}
+			elsif ($name eq "/FONT") { # 
+				# Revert to the previous font stack.
+				pop(@stackFont);
+				pop(@stackColor);
+				pop(@stackBG);
+				pop(@stackSize);
+				$style{family} = $stackFont[-1] || '';
+				$style{foreground} = $stackColor[-1] || '';
+				$style{background} = $stackBG[-1] || '';
+				$style{size} = $stackSize[-1] || '';
+			}
+			elsif ($name eq "A") { # 
+				# Make sure this link has an href.
+				if ($attr =~ /href="(.+?)"/i) {
+					my $href = $1;
+
+					# Find the target.
+					my $target = "_self";
+					if ($attr =~ /target="(.+?)"/i) {
+						$target = $1;
+					}
+
+					# Create a unique hyperlink tag.
+					my $linktag = join ("-",$target,$href);
+
+					# Store this tag.
+					$hyperlinks{$linktag} = {
+						href   => $href,
+						target => $target,
+					};
+
+					# Tell the tagger we're linking.
+					$style{hyperlink} = 1;
+					$style{linktag} = $linktag;
+				}
+			}
+			elsif ($name eq "/A") {
+				# We're not linking anymore.
+				$style{hyperlink} = 0;
+				$style{linktag} = '';
+			}
+			elsif ($name eq "BLOCKQUOTE") { # 
+ $cw->SUPER::insert ('end',"\x0a\x0a") if $lineWritten; + $style{margin} += 25; + push (@stackMargin,$style{margin}); + } + elsif ($name eq "/BLOCKQUOTE") { #
+ pop(@stackMargin); + $style{margin} = $stackMargin[-1] || 0; + $cw->SUPER::insert ('end',"\x0a\x0a"); + $lineWritten = 0; + } + elsif ($name eq "P") { #

+ $cw->SUPER::insert ('end',"\x0a\x0a") if $lineWritten; + } + elsif ($name eq "/P") { #

+ $cw->SUPER::insert ('end',"\x0a\x0a"); + $lineWritten = 0; + } + elsif ($name eq "BR") { #
+ $cw->SUPER::insert ('end',"\x0a"); + } + elsif ($name eq "PRE") { #
+				$cw->SUPER::insert ('end',"\x0a") if $lineWritten;
+				push (@stackFont,"Courier New");
+				$style{family} = "Courier New";
+				$style{pre} = 1;
+			}
+			elsif ($name eq "/PRE") { # 
+ pop(@stackFont); + $style{family} = $stackFont[-1] || ''; + $style{pre} = 0; + $cw->SUPER::insert ('end',"\x0a"); + } + elsif ($name =~ /^(CODE|TT)$/) { # , + push (@stackFont,"Courier New"); + $style{family} = "Courier New"; + } + elsif ($name =~ /^\/(CODE|TT)$/) { # , + pop(@stackFont); + $style{family} = $stackFont[-1] || ''; + } + elsif ($name =~ /^(CENTER|RIGHT|LEFT)$/) { #
, , + my $align = lc($name); + $cw->SUPER::insert ('end',"\x0a") if $lineWritten; + push (@stackAlign, $align); + $style{justify} = $align; + } + elsif ($name =~ /^\/(CENTER|RIGHT|LEFT)$/) { #
, , + pop(@stackAlign); + $style{justify} = $stackAlign[-1] || 'left'; + $cw->SUPER::insert ('end',"\x0a"); + } + elsif ($name =~ /^H(1|2|3|4|5|6|7)$/) { #

- + my $size = $cw->_heading ($1); + $cw->SUPER::insert ('end',"\x0a\x0a") if $lineWritten; + push (@stackSize, $size); + $style{size} = $size; + $style{weight} = "bold"; + } + elsif ($name =~ /^\/(H(1|2|3|4|5|6|7))$/) { #

- + pop(@stackSize); + my $newSize = $stackSize[-1] || ''; + $style{size} = $newSize; + $style{weight} = "normal"; + $cw->SUPER::insert ('end',"\x0a\x0a"); + $lineWritten = 0; + } + elsif ($name eq "SUP") { # + if (not length $style{size}) { + $style{size} = $default{size} - 1; + } + else { + $style{size}--; + } + $style{size} = 0 if $style{size} < 0; + $style{offset} += 4; + push (@stackOffset,$style{offset}); + push (@stackSize,$style{size}); + } + elsif ($name eq "SUB") { # + if (not length $style{size}) { + $style{size} = $default{size} - 1; + } + else { + $style{size}--; + } + $style{size} = 0 if $style{size} < 0; + $style{offset} -= 2; + push (@stackOffset,$style{offset}); + push (@stackSize,$style{size}); + } + elsif ($name =~ /^\/(SUP|SUB)$/) { # , + pop(@stackOffset); + pop(@stackSize); + $style{size} = $stackSize[-1] || ''; + $style{offset} = $stackOffset[-1] || 0; + } + elsif ($name =~ /^(B|STRONG)$/) { # , + $style{weight} = "bold"; + } + elsif ($name =~ /^\/(B|STRONG)$/) { # , + $style{weight} = "normal"; + } + elsif ($name =~ /^(I|EM)$/) { # , + $style{slant} = "italic"; + } + elsif ($name =~ /^\/(I|EM)$/) { # , + $style{slant} = "roman"; + } + elsif ($name =~ /^(U|INS)$/) { # , + $style{underline} = 1; + } + elsif ($name =~ /^\/(U|INS)$/) { # , + $style{underline} = 0; + } + elsif ($name =~ /^(S|DEL)$/) { # , + $style{overstrike} = 1; + } + elsif ($name =~ /^\/(S|DEL)$/) { # , + $style{overstrike} = 0; + } + next; + } + elsif ($sector =~ /^::END::TAG%/i) { + $sector =~ s/^::END::TAG%//i; # strip it + } + + # If we're titling, don't bother with tags. + if ($style{titling} == 1) { + # Add this to our page title. + $style{title} .= $sector; + next; + } + + # (Re)invent a new tag. + my $tag = join ("-", + $style{family} || $default{font}, + $style{size} || $default{size}, + $style{foreground} || $default{text}, + $style{background} || $default{bgcolor}, + $style{weight}, + $style{slant}, + $style{underline}, + $style{overstrike}, + $style{justify}, + $style{offset}, + $style{margin}, + $style{hyperlink}, + $style{linktag}, + $style{pre}, + ); + $tag =~ s/\s+/+/ig; # convert spaces to +'s. + + # Is this a special hyperlink tag? + my $color = $style{foreground} || $default{text}; + my $uline = $style{underline}; + my $size = (length $style{size} > 0) ? $style{size} : $default{size}; + my $ptsize = $cw->_size ($size); + if ($style{hyperlink} == 1) { + # Temporarily reset the color and underline. + $color = $default{link}; + $uline = 1; + } + + # Configure this tag. + $cw->SUPER::tagConfigure ($tag, + -foreground => $color, + -background => $style{background}, + -font => [ + -family => $style{family} || $default{font}, + -weight => $style{weight}, + -slant => $style{slant}, + -size => $ptsize, + -underline => $uline, + -overstrike => $style{overstrike}, + ], + -offset => $style{offset}, + -justify => $style{justify}, + -lmargin1 => $style{margin}, + -lmargin2 => $style{margin}, + ); + + # If this was a hyperlink... + if ($style{hyperlink} == 1) { + # Bind this tag to an event. + my $href = $hyperlinks{$style{linktag}}->{href}; + my $target = $hyperlinks{$style{linktag}}->{target}; + $cw->SUPER::tagBind ($tag,"", [ sub { + my ($parent,$href,$target) = @_; + + # Call our link command. + &{$cw->{hypertext}->{linkcommand}} ($parent,$href,$target); + }, $href, $target ]); + + # Set up the hand cursor. + $cw->SUPER::tagBind ($tag,"", sub { + $cw->SUPER::configure (-cursor => 'hand2'); + }); + $cw->SUPER::tagBind ($tag,"", sub { + $cw->SUPER::configure (-cursor => 'xterm'); + }); + } + + # If this was preformatted text, preserve the line endings and spacing. + if ($style{pre} == 1) { + # Leave it alone. + } + else { + $sector =~ s/\x0d//sg; + $sector =~ s/\x0a+//sg; + $sector =~ s/\s+/ /sg; + } + + # If we wrote something here, inform the rest of the program. + if (length $sector) { + $lineWritten = 1; + } + + # Filter escape codes. + while ($sector =~ /&#([^;]+?)\;/i) { + my $decimal = $1; + my $hex = sprintf ("%x", $decimal); + my $qm = quotemeta("&#$decimal;"); + my $chr = eval "0x$hex"; + my $char = chr($chr); + $sector =~ s~$qm~$char~i; + } + for (my $i = 0; $i < scalar(@escape) - 1; $i += 2) { + my $qm = quotemeta($escape[$i]); + my $rep = $escape[$i + 1]; + $sector =~ s~$qm~$rep~ig; + } + + # Finally, insert this bit of text. + $cw->SUPER::insert ('end',$sector,$tag); + } +} + +sub _size { + my ($cw,$size) = @_; + + # Calculate the point size based on the HTML size. + if ($size == 1) { + return 8; + } + elsif ($size == 2) { + return 9; + } + elsif ($size == 3) { + return 10; + } + elsif ($size == 4) { + return 12; + } + elsif ($size == 5) { + return 14; + } + elsif ($size <= 0) { + return 6; + } + elsif ($size >= 6) { + return 16; + } + + return 6; +} + +sub _heading { + my ($cw,$level) = @_; + + # Calculate the point size for each H level. + my %sizes = ( + 1 => 6, + 2 => 5, + 3 => 4, + 4 => 3, + 5 => 2, + 6 => 1, + 7 => 0, + ); + + return $sizes{$level}; +} + +1; + +=head1 NAME + +Tk::HyperText - Create and manipulate ROText widgets which render HTML code. + +=head1 SYNOPSIS + + my $hypertext = $mw->Scrolled ("HyperText", + -scrollbars => 'e', + -wrap => 'word', + -linkcommand => \&onLink, # what to do when
links are clicked + -titlecommand => \&onTitle, # what to do when s are found + )->pack (-fill => 'both', -expand => 1); + + # insert some HTML code + $hypertext->insert ("end","<body bgcolor=\"black\" text=\"yellow\">" + . "Hello, <b>world!</b></body>"); + +=head1 WIDGET-SPECIFIC OPTIONS + +=over 4 + +=item B<-rerender> + +Boolean. When true (the default), the ENTIRE contents of your HyperText widget will +be (re)rendered every time you modify it. In this way, if you insert, e.g. a "bold" +tag and don't close it, then insert new text, the new text should logically still be +in bold, and it would be when this flag is true. + +When false, only the newly inserted text will be rendered independently of what else +is already there. If re-rendering the page is too slow for you, try disabling this flag. + +=item B<-titlecommand> + +This should be a CODEREF pointing to a subroutine that will handle changes in a +page's title. While HTML code is being parsed, when a title tag is found, it will +call this method. + +The callback will received the following variables: + + $widget = a reference to the HyperText widget that wants to set a title. + $title = the text in the <title> tag. + +=item B<-linkcommand> + +This should be a CODEREF pointing to a subroutine that will handle the clicking +of hyperlinks. + +The callback will received the following variables: + + $widget = a reference to the HyperText widget that invoked the link. + $href = the value of the link's "href" attribute. + $target = the value of the link's "target" attribute. + +=item B<-attributes> + +This option will allow you to define all of the default settings for the display +of HTML pages. Here's an example: + + my $html = $mw->Scrolled ("HyperText", + -attributes => { + body => { + bgcolor => 'white', + text => 'black', + link => 'blue', + vlink => 'purple', + alink => 'red', + }, + font => { + family => 'Arial', + size => 3, + color => '', # inherit from <body> + back => '', # inherit from <body> + }, + }, + )->pack; + +=back + +=head1 DESCRIPTION + +Tk::HyperText is a derived Tk::ROText class which supports the automatic rendering +of HTML code. It's designed to be easily useable as a drop-in replacement to any +Tk::ROText widget. Rendering HTML code is as easy as B<insert>ing it as raw HTML, +as shown in the synopsis. + +=head1 WIDGET METHODS + +In addition to all of the methods exported by Tk::ROText and Tk::Text, the following +methods have special behaviors: + +=over 4 + +=item I<$text-E<gt>>B<insert> I<(where, html-code)> + +Insert new HTML code, and render it automatically. Note that currently, only inserting +to the "end" works. See L<"BUGS"> below. + +=item I<$text-E<gt>>B<delete> I<(start, end)> + +Delete content from the textbox. Note that currently you can only delete EVERYTHING. +See L<"BUGS"> below. + +=item I<$text-E<gt>>B<get> I<(start, end)> + +Get the HTML code back out of the widget. Note that currently this gets ALL of the code. +See L<"BUGS">. This returns the actual HTML code, not just the text that's been rendered. + +=item I<$text-E<gt>>B<clear> + +Clear the entire text widget display. + +=back + +=head1 SUPPORTED HTML + +The following HTML tags and attributes are fully supported by this module: + + <html>, <head> + <title> *calls -titlecommand when found + <body> (bgcolor, link, vlink, alink, text) + <basefont> (face, size, color) + <font> (face, size, color, back) + <a> (href, target) + <blockquote> + <p>, <br> + <pre> + <code>, <tt> + <center>, <right>, <left> + <h1> - <h6> + <sup>, <sub> + <b>, <strong> + <i>, <em> + <u>, <ins> + <s>, <del> + +=head1 EXAMPLE + +Run the `demo.pl` program included in the distribution for a demonstration. It's a +kind of simple web browser that views HTML pages in the "demolib" directory, and +supports hyperlinks that link from one page to another. + +=head1 BUGS + +As noted above, the B<insert> method only inserts at the end, B<delete> deletes +everything, and B<get> gets everything. I plan on coming up with a way to fix this +in a later version. + +There are some forms of HTML that might not render properly. For instance, if you +set E<lt>font back="yellow"E<gt>, then set E<lt>font color="red"E<gt>, and then +close the red font, it will also stop the yellow highlight color too. Situations +like this aren't too serious, though. So, if you set one attribute, set them all. ;) + +=head1 SEE ALSO + +L<Tk::ROText> and L<Tk::Text>. + +=head1 CHANGES + +0.03 x + - Added support for the <basefont> tag. + +0.02 June 20, 2007 + + - Bugfix: on consecutive insert() commands (without clearing it in between), + the entire content of the HTML already in the widget would be inserted again, + in addition to the new content. This has been fixed. + +0.01 June 20, 2007 + + - Initial release. + +=head1 AUTHOR + +Casey Kirsle, E<lt>casey at cuvou.netE<gt> + +=cut + diff --git a/lib/Win32/MediaPlayer.pm b/lib/Win32/MediaPlayer.pm new file mode 100644 index 0000000..e600c8d --- /dev/null +++ b/lib/Win32/MediaPlayer.pm @@ -0,0 +1,253 @@ +package Win32::MediaPlayer; + +use strict; +use warnings; +use vars qw($VERSION $self $mciSendString $result); +use Win32::API; +$VERSION = '0.2'; + +BEGIN { +$mciSendString = new Win32::API( + "winmm", + "mciSendString", + ['P', 'P', 'N', 'N'], 'N' +)|| die "Can't register mciSendString"; +} + + +sub new { +my $self = {}; +bless $self, $_[0]; +$self->{alias} = rand; +$self->{pos} = 0; +$self->{play} = 0; +return $self; +} + +sub load { +my $self = shift; +my $file = shift; +$result = doMM("open \"$file\" type mpegvideo alias ".$self->{alias}); +return $result; +} + +sub play { +my $self = shift; +my $pos = shift || $self->{pos}; +$self->{play} = 1; +$result = doMM("play ".$self->{alias}." from $pos"); +return $result; +} + +sub volume { +my $self = shift; +warn 'No File Loaded' if($self->{play}==0); +my $vol = shift; +if($vol ne '') { +$vol*=10; +$result = doMM("setaudio ".$self->{alias}." volume to $vol"); +return $result; +}else{ +return 'Null'; +} +} + +sub length { +my $self = shift; +my $flag = shift; +warn 'No File Loaded' if($self->{play}==0); +$result = doMM("status ".$self->{alias}." length"); +if($flag) { +my @time = localtime(int($result/1000)); +$result = sprintf("%02d:%02d",$time[1],$time[0]); +} +return $result; +} + +sub seek { +my $self = shift; +warn 'No File Loaded' if($self->{play}==0); +my $seektime = shift; +if($seektime=~/(\d{2}):(\d{2})/) { +$seektime = ($1*60+$2)*1000; +doMM("stop audiofile"); +$result = doMM("play ".$self->{alias}." from $seektime"); +}else{ +doMM("stop audiofile"); +$result = doMM("play ".$self->{alias}." from $seektime"); +} +return $result; +} + +sub pos { +my $self = shift; +my $flag = shift; +warn 'No File Loaded' if($self->{play}==0); +$result = doMM("status ".$self->{alias}." position"); +if($flag) { +my @time = localtime(int($result/1000)); +$result = sprintf("%02d:%02d",$time[1],$time[0]); +} +return $result; +} + +sub pause { +my $self = shift; +warn 'No File Loaded' if($self->{play}==0); +$result = doMM("pause ".$self->{alias}); +return $result; +} + +sub resume { +my $self = shift; +warn 'No File Loaded' if($self->{play}==0); +$result = doMM("resume ".$self->{alias}); +return $result; +} + +sub close { +my $self = shift; +$self->{play} = 0; +$result = doMM("close ".$self->{alias}); +return $result; +} + + + +sub doMM { + my($cmd) = @_; + my $ret = "\0" x 1025; + my $rc = $mciSendString->Call($cmd, $ret, 1024, 0); + if($rc == 0) { + $ret =~ s/\0*$//; + return $ret; + } else { + return "error '$cmd': $rc"; + } +} + +=pod + +=head1 NAME + +Win32::MediaPlayer - Module for playing sound MP3 / WMA / WAV / MIDI file on Win32 platforms + +=head1 SYNOPSIS + + use Win32::MediaPlayer; + + $winmm = new Win32::MediaPlayer; # new an object + $winmm->load('d:/10.mp3'); # Load music file disk, or an URL + $winmm->play; # Play the music + $winmm->volume(100); # Set volume after playing + $winmm->seek('00:32'); # seek to + + #$winmm->pause; # Pause music playing + #$winmm->resume; # Resume music playing + + print 'Total Length : '.$winmm->length(1),$/; # Show total time. + while(1) { + sleep 1; + print 'Now Position: '.$winmm->pos(1)."\r"; # Show now time. + }; + +=head1 DESCRIPTION + +This module allows playing of sound format like MP3 / WMA / WAV / MIDI on Win32 platforms using the MCI interface (which +depends on winmm.dll). + +=head1 REQUIREMENTS + +Only working on Win32, and you should installed the Win32::API + +if not you can install by ppm, in the console mode + +type command: + + ppm install http://www.bribes.org/perl/ppm/Win32-API.ppd + + +=head1 USAGE + +=head2 new + +The new method is the constructor. It will build a connection to the mci interface. + +$winmm = new Win32::MediaPlayer; # new an object + +=head2 load() + +$winmm->load('d:/10.mp3'); # Load music from the disk, or Internet URL. + +=head2 play + +$winmm->play; # Play the music file. + +=head2 seek() + +The value should be a format like XX:XX, or you can fill the micro second integer of the music. + +$winmm->seek(100000); # Seek the music file, at the 100 sec pos. + + +$winmm->seek('01:40'); # Seek the music file, at the 01 min 40 sec pos. + +=head2 close + +$winmm->close; # Close the music file. + +=head2 volume() + +The value is from 0 to 100 + +$winmm->volume(100); # Set volume to 100 after playing + + +=head2 length() + +Return the music total length + + +$length = $winmm->length(1); # Return the length in XX:XX format. + +$length = $winmm->length; # Return the length in micro second integer. + +=head2 pos() + +Return the music now position + +$length = $winmm->pos(1); # Return the Position in XX:XX format. + +$length = $winmm->pos; # Return the Position in micro second integer. + +=head2 pause + +Pause the music play + +$length = $winmm->pause; # Pause the music play. + +=head2 resume + +Resume the music play + +$length = $winmm->resume; # Resume the music play. + +=head1 AUTHOR + +Lilo Huang + +kenwu@cpan.org + +http://blog.yam.com/kenwu/ + +=head1 COPYRIGHT + +Copyright 2006 by Lilo Huang All Rights Reserved. + +You can use this module under the same terms as Perl itself. + +=cut + +__END__ + +1; \ No newline at end of file diff --git a/sfx/ding.wav b/sfx/ding.wav new file mode 100644 index 0000000000000000000000000000000000000000..0ed79692385db2a0e0ba1930d524ca3bbe7ee81e GIT binary patch literal 76972 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 literal 0 HcmV?d00001 diff --git a/sfx/link.wav b/sfx/link.wav new file mode 100644 index 0000000000000000000000000000000000000000..4596b1ed35607ce40b228fc37f3929891fbcb060 GIT binary patch literal 100383 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 literal 0 HcmV?d00001 diff --git a/sfx/message.wav b/sfx/message.wav new file mode 100644 index 0000000000000000000000000000000000000000..d9abe74a933f9c4c8f3554458ac1fa00a93894c7 GIT binary patch literal 4605 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 O3c&e)`4vt$qWKTspz9g{ literal 0 HcmV?d00001 diff --git a/web.gif b/web.gif new file mode 100644 index 0000000000000000000000000000000000000000..3409d7766c90a52cbca05b4276f944c64375c9f5 GIT binary patch literal 1577 zcmdUuX)~J%0D#{lZ{!qj2;xpiD(g&@iaT)zK~Ps6jXRF2tBNwsK^#RxDasN`sl}$n zs#Z-x9HlyG=}vdHAGFokZf9p(U1oKrGu_$!7yEp8K0jZckkDY3M>++d0Dl7j91eGP zcfWY?qOPtk3<g7^(HIPd&*wKbHVzF9nV6VZTU+bs=;Y<)4Gs=wWo2<VoSK>%i9`|< z6y)se91#&=Yim0<H%BBAJ32ZlD=Qls8mg<SD=I2-b92+v)2phg;^X7P!oo5#GQz{d zJ3BjDTU*baJLlu$<L&M3<>eI{8_Qy`m`o;@%Z-YP%FoXa4h~LDO?7Z^@bvVAAP9%U zVX;_^M#E;a<#PGGd-v+<>iYWn3JMAs42DP~vb3~(^yrb9nc490u(7f6jT<-I+}ti) zxZvU8ap}^f^XJdIxVX&D&eG|03WZ{4XQxuBKoCTsP!kgqr%s)Ui;G)ZTZ@j4_Ve?T z$z)|^W$o?lPEJmjFJBf4h0V>)#l^+fu3bAgILOJ#sjsiUe*HQUiJY36YHMo~2n0n% zMS6OAckbL79UXo6@ZreF$ol$vZ*Ol*Ow7%jH<e1|?c28<9Uars(hvwlc6RpDr%#WM zk4s8Qj*gC~RH|4k4h#%@@!|y@k3VtZ#M!fF$z(E(M%&uj>hJF_EiDZQ2v}WRB@hTR zGc#JP_R5tjhK7dY<KsO&JqZa3BogWQ^XL2f`z<Xk4<0-i7#JulEZp4Ow6U>CN=iCB zJiK-5R!B(5&d$#A@^V*Km#?p{zP|puckjBpyFY&XSX*2B`Sa(+#l?k%g~Y_f4<9}( zEiH|WjagY)DHIAGk9YOz)zHw;$;rv>?d{X2PiJOk+S}V(SXj)@&qqc^PESuKCnp2{ zXQ*Gm|MDIF^}kO5=x=}&W6qURL{#WcQp$$%wJLHz$m)L&sie4MMm|j_2!{I^SA72q z_heLkqAttz?qAZ86|?a7=KPb(>j>+i6sNC4vd#uU66M*(iWFsp^pu(;0?V)_LHp0N zV?O1jDawTxTJmMMRi6QndV1^LZevEF`-ha-Xrw}yY6RjaIy)##DXs1<aWYKm+C0EY zp$to#Jk(?j!$urR(2F;svSa9pVx~aAR<V^a?Izs4n!SRJRYw9I%jh`6csU4qPT-4m zHR3K?ENjJtPJ{op&;-o&WU#v--$_3B19cdbn5qHuFZ4jZOyE>r+}y!on>xE@A-`q9 zWeqaRK<jZBgbza+u&wV`k&s-180X^sX83IvU4OwG@CQBk5S(c}c_6|$IJ^!qH)Dym zjN^1^2n#idvr1O-+r!{8vI34mk>wgo317THHU$kd1PQUTP6vXt4H!Rd#$!9<-8WK9 z6Is1MrYEW-w9qsDG9ZYw%SEQV5~wk*cm;-0=B-_$3(tr`ET&0p3Eq7~puyuE0@Z{_ za7IE_ANxOsNV`-lJoW1*4uqRDY{6rc^IB>S4-nzJ@JzI&d{K!svZsgukbSfq4703! z#-~-IRpB_3S%^nX=+x%mgegN>Y#vFuVN7!URvrwMj%d+%8_)o5HO8~%z`JWQ#Bg0X z8-T_m_EosD-&M=#w#K_0lWY-3gRNz#lXT2^v{ju>u2^j0gO>YqB86fETK!V7nPgZC ztiTId@+SQdQ1RoEop7%aU}K<-K=8>P4CkgH)*Jbj?-irqg+#UC%&tOa7yvy{A<`NJ zYiO~CsopTqKnQFfR}0uN(4Nr<RwpEa#o}F5+wQ;0bH1DUW2isaaxuLQ$SM@AAtPK% z)Py3@ry#QTUa^xpmm+F3(vuVYk*M-o74D~gMF;}Tlz=e=A_U@1_XRMLJ99*YBne)x V=<}dgtJK){I^}`JNF)e&{R8@N5##^> literal 0 HcmV?d00001 diff --git a/worlds.gif b/worlds.gif new file mode 100644 index 0000000000000000000000000000000000000000..234802be224853384055277d80e5a061a657161b GIT binary patch literal 1936 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 WJm3o%5Jm=8fr})hz#2IS2mm_)>Q{*X literal 0 HcmV?d00001 diff --git a/worlds.ico b/worlds.ico new file mode 100644 index 0000000000000000000000000000000000000000..4fae27b4ba783328d5e3a36c2c3ff7e6a1e959ca GIT binary patch literal 15086 zcmeHOXK<8flIH5}$KCGUtvA@a*ak$-0tqC9azI(5oU=5_0SP2Ppa>R{MKT#o&Im*@ z*e06{CK;14V7%VF_4@2J>xA>;y?%Q1%46>Ks;;hTe;rlL_swW#-tK-oy-)Yot=m6z z`^Ro0M|P8|L$~(+-MYDU>(<S|q4V_<-MT%H_nwd||6cS@-MY2N2L{P!<SX5>h4{Yy z>u0;z|L+kN4X>CK_$FHsnpuM2j1mN;mmwgzP~OkPn6LzRC6vH7u}I!4fM;wL#zv;< z`y&D@aPkYoDBoZV8smfhqdd@O<QP0XU>JVeZwP+VXOI%_`QEcbobk+{5qj@1+(ka` zi6QO*wl$)X5fGmS&yYCyL?-EdOlBS|sW}Ks%tV~E6!9fhNSIiM)T%aQ)yzYF!(!## zJ2nH}aXA>5l8>OYB9WmK<5G*^6`KQ>@D#YkScPvZd=jj14T;4_zi<qeb^AKIqmR=V z^cmqId=9}=y$9jRUIXyN(++r|y90jy+g?h%|Hae2@yloX;TO;J(feQb>@S}mjPCu0 zq1P}M3=r9ed-!3LcMv>6qlEt?#AI0!k(!O5gmi?Y<RUt+0?8G1$gP<TYwZH$H7-}Z zlXtJkZ1^T*sSMl)?txc)9!6U-l-!~+MTTrR$?p#ky-}Cc@2?&D;}^a9;-7#1)c=Vn zD}Qh3Xm^Ym7mRW7@rcW@B0M!q_rfbW6%m=GNGq#HY5hDDHY`D2(<-%@fP{SIpSIy0 zM>%Y=@SZ%=P6m4gp|6{du3_wgd>i`dZ+hZqokUrF@~iIp`f<5_@~fxyeR~_@clka) z%llq~9N|3H2ehAryfUPeP6GK4&zYdMQ!-;A3Y!+G{e)%|z&{}uo|a6JQ}izsDEr_S zp8?OvWQ+-oL;ulU=<PHXJ;hGC4;YT_{fDZ3KiOv>e${&*o^Tk5e|yHEi+=W-XYsGy zd+O_-J@qVp`s6dp-@p8lGWF2+pBd-~Lr;nw{jx_twKeL1Hb@!#!ed}bvnv1Ii8%<h zPC!EC4CK}?L{8lzWY;c2Sh^Mdaar()NLN3huaM|V9-%Sno79yddmrR`Pm29M(PvoK zKA8Qmzfv~xMcwkAf8+1;96Ad0V-jWIzS{32_aHDfRr&WxFGfh-M8uTWB5mrTF5=!q z=9Fu^p^qmQmLsFQ0*p&T-TiFqjBr!^_aE&cd*RW!7jCj2Zg3F4qg@U05B!@n)He{r z#Bcbyfgb*PKf*6mVvoQ6UY`+T(A&{f<*?Te<r(1Y1{eQO<(s-^?1`H=2MN_LAZf}X z6i#bKTE#R(=T{=DvJROQHOQ!_#xT!u=qJ4K_sC-(7ccaa7}?L&6aD2nV5~oe2S>xn z5)aq-6pWF0;S!sKQ89@a6%_@i2n$Apg=0i$I7Ws>U}Q)HhK>t?Q?LcDVw2tp8E}tG z#F&sM3>oW(0o04g&~x}`^cdo#akhK^Ve*~}2Dy46SYnE$a4O;|T9yCg$qSHCy9|@t z7NNMl1(_44s$b_#c~0-C+a40v$UpVgSJvp~K2Glki!Miuv%oV;azfE~giWZ%xWW?n zSc~DAT?DU;RE$kZ6nWzQ3%SQhu95g)kysus`%U{DJuY1Lnz@5}(Mx3Dy7ol>4a}^B zrL0ByPn)_}<If8nFXOq@?Wmc*N@ykGizZ5Z54Gvt*+Y5m?-hZ8J|Su!<T-3YJ(48` zC^fYpxv^Gd2o<{vDK14&US1bb7JKqd6W>bBMR;Cm7g>tRB;RDgJt9tRh<;&sr!16< zKH}&Trty$6BnkhyEgL}oTh?ty!;*C<m_7#qDONZ*xub`pJ9@kLqMt_y$iGuW61*iA z#MHJSamIY4&sv6*=Vl|RaXJ!eYY;I}^jTSnu<|kl7Zi4pZ%z*Ua<bu<Eio-yc+ZkN zDS0V8zqpGm1>&381qhJeA1XdUyQK_D^zTmp806-MkrK;8^Ba&@JrCCB)++zAHg;g< zicOlEox|cap7a{!k3Md}$~*b@l-eP#dO8Z0ZijX824pW>g|y~oB-Pg;ZptJ?PpU>_ z)dZ1ayzT+_fke9s%rETP53@HBg@x#%Jl>b9O^pd*-0)X@Q|G<t6JpD>tG>=-)!&0M zCnKSvP3@;`{dP>7zZ~UFty+t)Cg9$Y=K(&E7~~g?fZ{qN%vgqkRr`L#zjgLPWVOy# zIVeL^wT*wuU?_Tg6(Y;3l()z4`PxuuUWwWj^DkqLu^D4a_HmD~KB}Nj`EOmb6=jXj zBPORre10UJ9qOXCL*9o4C%`#69dXU8k-1<SO4huJk`=p9xNH;h7Oz15{3XbpGhby$ zm^KBmQ>s;lNU@{H2~~)ynj&$Id)<g6p_s~Ph>`dfGhRMhDKxQ0_Ou3;;tHu_GBG+> z^d<cFA2n8Wtul!I=>yE4y++z%_Nc%ZMC4X${WEjrCS;7Sfs5D`bH|hYM}oFPy_0vw z6YIKTC|Y*}<JTR;__g~`x?(emmM&MK4BQ9$F#SMnN%R^sL1LZQTUy<0WY*6{Zo_PS z9beU;cFH<3VRAhZr!}Y_yNU0b{9=El%}@^7kRj%hkj!$e9~g7Oq=tBQu(R6z(~fQ$ zbDX2o5IVjQ*>g83|M_bUs{N4v39ENw(yATGx3M4cVTd`>Qal-9`4g1qO!0ZvEcwl1 zZ;f-1GObnR;hr##GM6#mum)#6Jy`sd_ga(7mGPT0^%4D>d_68c6UB9{m@;P>f|7Ia zl+@@?4H$uEMNY<g_w-U&CeKFpg6+s%aRAmAUq;^Yy`ukDFi~Wfvi2q9+Sz|>RSlxb zB?c9WEhOc@M}FTcCJp`)&qA|G5nWJ)qzMg3o-{-GPpf%O^3eqOY!*iP1jErwa<usE z2<8?cM<Ko*EW8XLF@KJddYv{TvB}$#D7kj3<}cPdrY7m-B>aoK^x4?PrN~+Gl2Uf( zl{Qqpem|ydeDz2Aryqvo6l-pxKFJenZPIATbvy^LE{`dzQT}N^<0M`(cbU)e%38ri z-g60(vyr?WF8jhg;a*S%?gQh?n4m~K)3E+=mi1U)(Y|_*_E!6enK}n)tt&O&PzLLg zoqErlkv4rL66zPL{iRQxr8YvIhf9skwYaXcPmtDrJexTSPtL*<KjR|nGEXs1jusyI zzN^#}qx{44dyGGhQlqeTqb%f&`INtF$o`BFmXe2|VsGT1v9_<sILI>)$<HlE#;mol zF4(Fzlr?V?vgWKoM)QkEX<DZIXG<L$UpXDY)=K4ln0#-j=*yY>ck(?tAWU^UIxs@c zZZU8Ri|yn;3N8|FD8mSmb*PIw1`TyYcR9m7(`PVxh&>M(J_5r=yJ5K0k<^VL$FaT` z6A%I)ON^d3`-$GkKYfFHME}kbzoqYzc+b5sHA7<KBIQ55VLl>?YBZ-)H{_k)wbwcA zfaEH?vWD@Hn$75+dqARX^cyq;4&8rzhU4r018iq9waL!C;P1PNUfkt3xfeYLJ9gRs zz%kysr{p!h{sqvEXe;EI?~&)!rUiN*WSs=J$aLLT@;zK^o_o#n+Ni)N<;^Q90lwm2 zta(U&;@hm>c<&i4HLJ+x*smW3_UxhjlV?L7{dywQaUhc2oK!E4zF|^7$exMKjgkE% z|9yr_tu$~rc$T7H82%HcwIOzD8=|JPDgQ|g3zhHm`XBfY$S8wrc#`rz!Y>5Qq7xVS zUGmNvd8|cn1UYvHrWYV6OKKd+k)gR|2(ykyc)@u1%Q?v{BoZU~IKZ)Y4-9vB1|Ea@ z!n2d|yhfoa#1qfO1tKUY6z<{=!@Xr4@fBC{FSR$%P~8U%?ZP(<|I9tnlbR7ZxmEd3 z7F(z8?e$O0#YOm}?HE7gxra6}){?At#T;OWv7WhCbC<-Qkjw%-Cpio6uA*y?fexx` z>YFq#ITUNGG1xUe6}izC1O-YBENf{T5dOIqV?{s7Z#?TThYfb~Ra>>^pE)$Gey;LP z%q4%ioSh{X(FQ!C5_Lam(;jk`QF{@&f+Rmi<V&p}xh!$gOxt(mJH@k>qin%i&3{?7 zty=$+|G;53JF(~a!u%<?vaAj_*UZ3z{A6TA@tokTw&cV;5c_ctjmAKUsl-8uEe#_7 z5+@QSHzT&X2~iX4C0@5l9Wckn19|VvrD53;@5Q!xhN5433I^pVJ{pu$fv^Ip=gR7o z_f+wt%$YBuVo^J)SMEm5hF8(J{TNE;twr+0M!5FviFnuHYAag`;<2|n3&&=Z;L6fE zTwO8^*A~@aYHA!}Bf>Dm+fU+zKSqiVy98Pg5SJ$BHy`z1+R~sg5}PE~FvrJN*K2*q z`&8Kj+79(@C|1s#K`A*JPZ(P~V+6Ahyd|Qf7Eu)qniu$;%$X}Rj!oP28X9+;Mf1y- zG3($B)JPsIXj`dvljk#9?d8~{ESzg8$D5scC+}~zRfzpWA}CbyfSj|r2P3@$wPqs^ zPU07uf5m=SKhwsz?+I1YZMojA1|#o9ZA(RWD==}{MybtOwf-Xh<(DkDm&Bx)@@Aw6 zM!;NG@!}y&*?1OBJFjEzu?JXu;RCF?^#$gh{twJO@dtgcP-@U=1vx+P-&m+TUv4hJ z?&=)19oi>zALASOcb53i_cFv!J*1ZSt?0hjFsW1ZoZt>i(IlkQ%tZe4OORSCy6=>s zpk*n_URaBX%iA$y%gd-*yiV+CuJRs`QK9_DO`MI4Cdu9NI#Abs4lS?V!u(V3VeQQi zvG&$Othx0GmR$O?(1%zmRJ-OSG`7yijLPxYT$zJilk#z(wip{G6<}s@4vI3;)jk;) zoCCxM#C}{QZuyJtvMvlvk~3rHIg{sq#!<$M%!XO2WA$J9u*gv;`(3%TUF)Akubx3$ z$00PX-=lm}_ax#PIW3z|y7U#blO^Zh!@9S=#@;`FgKh7AhV{2U!s;7;5jj5B_ZOW1 z5YKHprnT7Y(mX6JvtqqaO@1!YGi>>S_Bl43dtg!jWvt-Yzi!!9#1>X*4rKjfFbBq# z<Vj5^msr6V8ZWtk{L_9)=h|#%&Yt5~bmRh7zJ8<2o+)2S!wTfh-mbPoJ6Uz}Q*`|4 zTU_|~5pF*G4DWvQsoo#@=xcO*04%@q5c5tvP`{u}a?j~=jJM1i%y*1y<e4@@?AR|h zU2(02+xH`9QX`zipP%OWORyK#r>;_`go|(FOrI-fl2(mDJcrPJDi*9m&8ls9Vc$tC zIeJm~Uw-LNSa9+-W*s;ud$S+Y+h4=1ooBJ}^846x?_(T&|0~@4^b7p)^Dpt2FTTQq zM_=ROCtqX#N8e!8^^Y+B#62|cIgg6P>tUU_07;Xl%b8g448c1BrM@Ts?h&zYlX%HG zB|>sXxttrf9zBN%f+H{&Fb5FFV;w{peG;=I*VL*mGV0p&%t8H5T(VhhhW4}UwM)wX zmfwAY?SJ?JyZ-dKzFv3cIyRrYhC_GW$C-B@;_3&F@bJ->c=YwRz+eB0&*XCd(Kk5r z@mFe33s2rb^Ul+{-_#j%95I%#>_W8@@;_GaP{w@LDoMqan6YFPI!;`~>@}NFTHE*| z{wV`-F_)kSwHf9e+7fNOY17M?vF#x2?T7Zf>76gs9xmW}JopaEBYFK>^p$*$a%{aR zap}bCn7&i$bU70xH8k1I|AI?|<rLX4a*6Y<;n5mD8S^L1oR4+;&tT`tOIRit(DX$s zF~rRqeWf4KWAI2la}xjLSt3&~xiZ0frmpV5jLrKneaow8YCnkQcb&xC{TH$D_zkSM z`VbrLKSIa*-+}z!`v&;)SKr~=@4v@)LX^S&p0B@=_bJ2a-@T7@r(Q?h=61ybk{TLp zScc#(;W>E-%apoLuspZ0FsTjwkWp5NmSt<P=j3I)bmj^+9XN&MJNB!M4R!a$(}Fwl zoHN|RU(XOc<5bLAik5Brm1eXbKx6wM-S5_Ym(+LZgX`~oZu3*&>CQLb;k_@v#n-|k z`6j>i_x5}8?nj?s$C>k}S-tK@m=f`!aBB|2v(n%b6OOTA<D^#cLQP96R&Cpb<Ckw? z<7-#2;pk;-IC4obMD_y|{}KL2c#YE<l|COSy&GZ(l)><Cli?I*?Y*RVh_Re;^62N^ z<HA=W!*@3OppNbNHTRT@GLcT-zlRqN9hBTxgqV_i#1!WWS!LZ!glDEI-+plxMCN8< z^TD&&aq>;<yL1=ZPF%s3<5#fp*cHqZ-tuc(q?eJP`X4E0Y7fDf8Mnqsoj@5XW-Y;# z7u!|;)1`)K-gyeGyI+?abq6n;`XiQK{S+^K@Eu<J1S*5v;#1+DywYC26#7i)VJCeg z*LMNDeCI7ZzwZ@9<Ygejnl5~%A*Lu7mfQ@4W+cHqJOFV8R+P?Y#@6F+VEgIo*meFb zwV#etZ)&XAeBv6W3${Znn)Wl&J6O+W_OX|?RMxi4W<NVls|_)3H0`*K=MTRF<|4)- z%5db7@b>**K^~2sKb89r$+Ns~i2kzq=56)g;Iue|ro|&XJwf#!A#wyu?dBI7hWx41 zK)i7KxjS7%o;MwTQ|)NSnQQ1c^@heI<~Dcnch(x@zl)+%5RjCsF@dpklGKxi|CU$o zV$PA@s|-u8e5`d2^V5mXptf}S;kVfT!MExo%*9*ozNfL4SXg>vz1j@<j>t+;JBhSr zA|fYU?PrPjcKd<XaQM<q91`M{v{h_p^U+J#boc_=k6pwzq3uE?QaeP+S;-|>&Qekz zj}4Di8GNOm<0se-Wk?u54Vkrbq@G)YO4ex`PNTl#4YcgPk6A~4Cpq=svGV#~(DC+X zIQ0G(*n0hathxLymcITLW*t6@#%=phy?iZF8yclnoM6+p@NUUT19^`W+esZ?hJtCe zXg_oUFP*w*<NM+b91-4+3T>18v-R+K@rg6)7hB|>@6B4jSA3yOb2n=~%D_D!Rzw+C zkMKTN`cgcnu+}VkVW-v%bz3f@VcVNpbIv*Xdn|taeXVy`Gq>zHtGPol$El5o6zrdI zCbG!pv&y&fF8s^2sIFe~)S)YParpA>F8!1D<CkyZmGd{T_uO^t7TR&<s_qA4(dLs^ zB_>_e9FaG9hUDu)&1H;59#JXqkUHNtUh0zQOn8J#eJGePdzh@Fz2Zxx9+f^udYPQV zCL=UwipppynW$JTd8U1aXXlH*7HG_gl=_)*GAuJque_(t>^yb~XYam)W7prZ(KXxk zwHx<vRPx^5^S7}3^_$pvPU!3{bey>%zI{!6`<A?ClWF$)U8rhZfVA>z>8(mE79Vhn zNQAf4P~LK8Q5mGq?P<wU{ym~|<!@GzFAsi+?B$nB|Efaj0m0StiV>NgFFfXnZChcn z=1PqqJ<lwgo##xfM49vyraa$@j-w}V;NlgWzVr4E{CCQ6{N_EJxcvaT&)>$5bGOx> zcb&hBJs0k3T-|^59`;|mj|0~p=<5}G4(mR$ryLNQtaU7Hh_eS;A5!m~#Jy0GebK&t zoK<Hhh#!V$=c!#pNNqsgX(Q~l2d5;d-kF=`u3C)^d-vhg?R#DHxc*Pxy07~}AAaTh z4eU90O-N#@#305Y-tU#VnD%8)%+pPa*DCfKQ&1_fxk&jZzhlFt-z{fPV@sZt$4*B2 zi+^ma#=@ZV6tT$+#AQoNuWdxr{6%6fTXo%6FW*tyICNF?M?DH1z9IH+N5^j7RoV7m zx{ZAoY&Jxmcb~nAU8moa{B{L9PP~Dg$1kHpa$U!%t3ubX{@^(*-FXDFH|)W*1#3|C z{9=?f%|>#`6vWAy$C6!&U^x%@#mT&c%&B;Zt+AKS8aUosj6~^mB^DP+Y-m7ba~taB zF2=$wJF)tu1K4)tBwl^vj`Dm+Z2RE#w`Kp|7XE1mcjfgx9K3N~uJ>_7uE%cPSKD~$ z!X33E`tTm%pEc1Bap|nq7OXK?YcO|oNc^L%(a%@!JE64``>^GWvp`>GA3P*QbkDpY z`VUAD`xbvKteT3Fsd6TgUKRT$OSZp)%?HkcdOj??(jQ6W{lKkvY;;5JuRqW|*iU)* zzPvsp@AI?vvq1lU|M%Ab=d>Jze1*E^v}Bg+@qav@nHep{b<Ss**(}}@_u*^et7aaH zeJf&0JlBzU&)Sw(Js-&#kKdtRbMA^)#y2x>#WNV^9f<jHZH;Ae7B=%qW;V&pCGoep z4zZxe=XU<5*ZdA~Y%_C2K4|x5*2v5maec1EcuV3v<z>Ie$jG%x#Pdm9*IW$`d~Nh% z{>H1B8Dfpe^(njd-9&!+ALF9^d=2%?-?7&#@Bh|34c8zkRwlaQ9+1ep`5SrQ`ka@s zpOxWz+-JrHGao~J6Ek70OfvmR>f^tghvB)If8&)k5x-;l%*HOri<x(!{G4?m9>n-$ zW<+@9TKo=w&&-ABOh`88D{S*1W)|e}IS}q0u@&l+y#Vq+;_n%v{>`ih_n&hTwi%CA z$~!;Db=jw<tX#|VXV?oS5B9Sh_A?yBmdyMHYa!ND+y}F^(FyH~JeWBR#$5UY_m@3t zu1zv?4Q8gn%rn@}GH{N;%r5Y1Kf6F(5Zg2MK;4oD?horX%FlC$nNc90T-VGd6n4%f zaDCRLoU0%YW(I**_BXkX{ngAKP#^R^Be(v3r#;c;xjtoQkDhjF`tznYZ~F44CvW=k zrWbGe@TLcE`WL46ZtT}w$%nCJ+A8A@_nv%F_uO~(*G+Go^0TjQdg_$lzL#$L=%$Bm z`sdUK{eb;9V%EHpXw&4y><9bgw14Bb{5$*0rnk&1bzpkRT%Wq7o%8qEV<9otGInx( z>cGBttbJp_M)^6z3wfZ-^m+O}bx*xee)jb^OT)E!rHz`NF!jxK`8)hQ#wikeOnjgA zu2{0XCZ5L6aQ{uu*L;`XH*uNyf$LKz+!NCq<<<Bd*Jr<he#7tbb7o!p9;jKLJW${4 z7n<12d#*_yNc_I(a~l0q4z6i>o2IYHEAeZyw*H3X2d<-MKe2P#I`@TF)4MeNN?zHs zH2q4`tK|Cjah~f_$ELSOnMquqd6Mh%Gp3hEec1Q#jQ#WP?0wny?b!da_3I+_cPIlt z$30?fraf@a%=*OkxwiJ@1RJMZv@7bEGLs+Ehtto=XNZH+R~Ub}zUdWmUH05ezs>a8 zxE^yd^-n#}j%mZRabC^u5}S{eGli}#@tbn97e!wA8QKbaP|YvDp_m`>56VuN*>^HM zC(~~-y(Y#T{;t`7>X*5ZYg2y473~Xj_LLagSu2=clIbIv9#U8T=-2}kR!{w5ed%+t zXT)A3WuWe<1Ku-s(DoUdxCh)%_6kgIg|g89iHY0yQ<ytVAI0=gO#g(hiB~bUuy3-y zTzV!2Hv44$Grbb-Q{A$5^%awMxzDCILLMkT`5~rl`XS_ry%6@$*atB^5YzuKy$|jO zze{~GCelA>JCvKcX6$K~J^}5JHb_12ecCa<L;Z6t%1!yXJ~24c3ov~E(*q#>N!;9E z{|5Kx?<wXl@^XEH^&6buVEiU7Q$Lj3;Q9vB=laC+DIc*tu4OP+`zzmXzxhrVZm;=L z&gG2Jw0GJcuf*wjrZX5feSoo<F`K$ExVFKxi4zgeR(;F*2FK>w#3Ai@;9eNqT5~or zYr$TKR~xLl3#abHsA+#(pK*zMWN>M&PcnG4V$p(u8XVewUBe6c>B5}_Zzkr<^|>~& z31ZE}nJGW_k!0{$<^*FW)Ps><@l5I08Z7px@Ib89V6gW1EBA+e6XLE0bLBnpR(q_K z>+<}p7_0Pr=o3G}R>fbqZ?r?RKCwIUK!2w#P!Fdcey96F{FHXWnnh(6+|*!q2KzD? zDX%=wQx_zIFPi*L9hfr<uLe(~-w{h>+#xTtN!kYOlr@sU4i)DYU7Pi3?}v#O%DN=( zKQSEQBb48U4_@oS2I&X11IAnO#F~x77|3ULg3@g`Ao-$y8~l$L6#EU7*AOwkAK`t1 zmk}o*PDbp4xCQNjm=19S-jiRR8_5Ubmc|dREo9FN&oA5~%E`5P<(ZFb6ZhI^hk143 zU9vv;p&lr|(E+cI<65lKm?w-5n5Vk*(8(9~hqguhiqE$&rch@7o?<70r96&T5r?Ck z)B!(d@EzI$_rl;h2Ge1jU`#hy4()?_q0KVi82pCcCw9YiNnMytCthQ)8vgBZJmByB znm7ev2?wDroI;*8d;A~qP(FjjXRka3_vEuyeVp9%*(yFqrO#45(?_43;xkixR*KI^ z@!2Ro6UAq7_zVu8z2VF-pS9sLHhk`c&(!c)8a_kAJ~f}2;j=P)MuyME@R^wO3h7By z)aVR5pSj?(7EY1z`fLTCso=8|P7*gc@55&%_^bq<k>Iltd?tdk_?*G-?c%31_nfuo zj6G-TIakkFdd|>uex5V)oR#k-`H-{moQdZw_OnB!MwZ!Y&RlcWnlsj%t&Ws4DQBrU zL(SP~&P;Pw+QCs~q-Cy^Gtr#I<qR%oZ>=xdW^Fkm%-LGb)N+=VGqjO1JIk3_&dPE| zmb0;(iRCONXD~T?$(c*eT5`scvz45wWNpY9O3qGlW|Fg#IwR>N=M$NU;Vd3!*f@J< zX6`s+$Jsj0)Nz)MGj#q*nL0DaSvk(gaW;-Kah%2C3>IgvICI5WE8lqOO~`B&XR0_$ z#TlwHnVsUy6lbM4BgNS$&O~t*hch^wz2VFaXKgrR!`T|n)Nq!DGc=r?;mizYWjG_l z8lN*UoW<Y_24^og_r+NY&RB4^Vus9AaF&8I6r7#l%mim8I3vN?2+l;X7tbC%d++SI zv)9fZ`-Zn4;hxkB?4^^~3uVuoz4GwfJnfCMC(d3hd$8=ivggWPD|@W$t+J=eUMhR2 z?4Pn{YLQ;4#VR<u^hP^Q-_TwhdvH<KGVQss*Tx<jdu!~ev6sdk8hdB#nXy;K9vORM z?1`}#!ye4eb2qf-!d?q|EbOhYr@~$ednoLkuxG+v340`rx9o{17B3h!v3FwbOLrYn ijGfpzpEDztP7IycIWcqM`^3nJjS~}pJRbaSU;ihK+@op$ literal 0 HcmV?d00001 diff --git a/worlds.png b/worlds.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5397c09b5abcdf46a0a2ce447f94d0bc3bf415 GIT binary patch literal 1944 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 literal 0 HcmV?d00001