#!/usr/bin/perl
package Net::OICQ::Win32Client;
use strict;
use warnings;
use Net::OICQ::TextConsole;
use Win32::GUI ();
use Data::Dumper;
our @ISA = qw(Net::OICQ::TextConsole);
our $ModInfo = "Net::OICQ v$Net::OICQ::VERSION Crypt::OICQ v$Crypt::OICQ::VERSION";
sub new {
my ($class, $oicq) = @_;
defined $oicq or $oicq = new Net::OICQ;
my $main = create_main(600, 600);
my $self = {
OICQ => $oicq,
Main => $main,
ChatWindow => {},
};
#print Data::Dumper->Dump([$main]);
return bless($self, $class);
}
sub create_main {
my ($gui_w, $gui_h) = @_;
my $gui = Win32::GUI::Window->new(-name => 'Main', -title => $ModInfo,
-size => [$gui_w, $gui_h]);
$gui->AddLabel(-name => "MyInfo", -title => "", -pos => [0, 0], -size => [$gui_w -8, 20]);
$gui->AddTextfield(-name => "Input", -readonly => 0, -default => 1,
-pos => [0, $gui_h - 80], -size => [$gui_w - 8, 80],
-multiline => 1, -autohscroll => 1, -autovscroll => 1);
$gui->AddTextfield(-name => "Output", -readonly => 1,
-pos => [0, 20], -size => [$gui_w - 8, $gui_h - 100],
-multiline => 1, -autohscroll => 1, -autovscroll => 1);
$gui->{Input}->SetFocus();
$gui->Center();
return $gui;
}
sub set_myinfo {
my ($self, $text) = @_;
$self->{Main}->{MyInfo}->Text($text);
}
sub get_chat_window {
my ($self, $qq, $group) = @_;
die "Bad arg for get_chat_window: $qq\n" unless $qq =~ /^\d+$/;
my $cw = $self->{ChatWindow};
my $winid = $group ? $group : $qq;
return $cw->{$winid} if exists $cw->{$winid};
my $dt = Win32::GUI::GetDesktopWindow();
my $dtw = Win32::GUI::Width($dt);
my $dth = Win32::GUI::Height($dt);
my $win_l = sprintf '%.0f', (($winid % 2) ? $dtw/2 : 0) + rand(50);
my $win_t = sprintf '%.0f', (length($winid)-5)*10+rand(10);
my $oicq = $self->{OICQ};
my $myid = $oicq->{Id};
my ($width, $height) = (500, 500);
# Note: the word "group" in the window title will be used to check whether it is
# a chat window for a group
my $chatwin = Win32::GUI::Window->new(-name => 'Chat',
-title => $group ? "$myid chat in group $group" : "$myid chat with $qq",
-pos => [$win_l, $win_t], -size => [$width, $height]);
$chatwin->AddLabel(-name => "ChatInfo",
-title => $group ? "$myid -> group $group" : "$myid -> $qq",
-pos => [0, 0], -size => [$width -8, 20]);
$chatwin->AddTextfield(-name => "ChatOutput", -readonly => 1,
-pos => [0, 20], -size => [$width - 8, $height -120],
-multiline => 1, -autohscroll => 1, -autovscroll => 1);
$chatwin->AddTextfield(-name => "ChatInput", -readonly => 0, -default => 1,
-pos => [0, $height - 100], -size => [$width - 50, 80],
-multiline => 1, -autohscroll => 1, -autovscroll => 1);
$chatwin->AddButton(-name => "ChatCancelButton", -title => 'Cancel', -cancel => 1,
-pos => [$width - 48, $height - 80], -size => [40, 20]);
$cw->{$winid} = $chatwin;
return $chatwin;
}
sub info {
my ($self, @text) = @_;
my $output = $self->{Main}->{Output};
$output->Append(join('', map {s/([^\r]?)\n/$1\r\n/g; $_} @text));
}
sub warn {
my ($self, @text) = @_;
$self->info("Warning:\n", @text);
}
sub error {
my ($self, @text) = @_;
$self->info("Error:\n", @text);
}
sub mesg {
my ($self, $time, $group, $srcid, $text, $font) = @_;
my $win = $self->get_chat_window($srcid, $group);
return 0 unless defined $win;
$win->{ChatInput}->SetFocus();
$win->Show();
my $output = $win->{ChatOutput};
unless (defined $time) {
$output->Append(substr(localtime, 11, 9) . "$srcid\r\n$text\r\n");
if ($font) {
$output->Append("$font\r\n");
}
return;
}
my $oicq = $self->{OICQ};
my $nick = $oicq->get_nickname($srcid);
my $id_color = $self->id_color($srcid);
my $srcinfo = $oicq->{Info}->{$srcid};
my $addr = $srcinfo->{Addr} || 'unknown';
my $ver = defined $srcinfo->{Client} ? "0x$srcinfo->{Client}" : 'unknown';
$output->Append(substr(localtime($time), 11, 9) . ($group ? "Group $group " : " ") .
"$nick($srcid, IP $addr, version $ver)\r\n$text\r\n");
if ($font) {
$output->Append("$font\r\n");
}
return;
}
sub prompt {}
sub loop {
my ($self) = @_;
my $oicq = $self->{OICQ};
my $select = new IO::Select($oicq->{Socket});
my $select_t = 0.1;
my $noack = 0;
LOOP: while(1) { #Win32::GUI::DoEvents != -1) {
$oicq->keepalive if time - $oicq->{LastKeepaliveTime} >= 60;
if (time - $oicq->{LastSvrAck} > 120) {
unless ($noack) {
$self->warn(substr(localtime(), 11, 9) . "no response from server for > 2 minutes\r\n");
$noack = 1;
}
} else {
if ($noack) {
$noack = 0;
$self->info(substr(localtime(), 11, 9) . "got server response\r\n");
}
}
Win32::GUI::DoEvents();
my ($socket) = $select->can_read($select_t);
next LOOP unless defined $socket;
my $packet;
$socket->recv($packet, 0x4000);
foreach my $data ($oicq->get_data($packet)) {
my $event = new Net::OICQ::ServerEvent($data, $oicq);
next unless defined($event) && defined($event->{Data});
$event->parse;
my $cmd = "ui_".$event->cmd;
eval {$self->$cmd($event)};
if ($@) {
$self->error($@);
}
}
}
}
sub ui_keep_alive {
my ($self, $event) = @_;
$self->info(substr(localtime(), 11, 9) .join(', ', @{$event->{ServerInfo}}) . "\r\n");
}
# Handle key stroke event for Textfield input
# Do something for two consecutive ENTER keys
sub text_input_handler {
my ($self, $input, $key, $callback, @cb_args) = @_;
return 1 unless $key == 0x0d;
my $line0 = $input->GetLine(0);
return 0 if !defined($line0) or $line0 eq "";
my $line_count = $input->GetLineCount();
return 1 unless $line_count > 2;
my $last_input = $input->GetLine($line_count - 2);
#print "line_count=$line_count last line len=", length($last_input), "\n";
if (!defined($last_input) or length($last_input) == 0) {
my $text = "";
for (my $i = 0; $i < $line_count; $i++) {
my $line = $input->GetLine($i);
next unless defined $line;
$text .= $line . "\r\n";
}
return 1 unless $text =~ /\S/s;
$input->SelectAll();
$input->ReplaceSel('');
return $callback->($self, $text, @cb_args);
} else {
return 1;
}
}
package main;
import Net::OICQ::Win32Client;
my ($QQ, $PW, $Mode, $Proto, $Proxy);
my $Login = create_login_window();
Win32::GUI::Dialog();
my $GUI = new Net::OICQ::Win32Client;
$GUI->set_myinfo("Not logged in.");
$GUI->{Main}->Show();
our $oicq = $GUI->{OICQ};
if ($QQ and $PW) {
$oicq->{Debug} = 9;
if ($oicq->login($QQ, $PW, $Mode, $Proto, $Proxy)) {
$GUI->set_myinfo("QQ $QQ $Mode mode, $Proto connection, " . ($Proxy ? "Proxy $Proxy" : "no proxy"));
$oicq->get_friends_list;
$oicq->get_online_friends;
$oicq->get_user_info($QQ);
}
$GUI->loop;
} else {
Win32::GUI::Dialog();
}
exit;
sub create_login_window {
use integer;
my ($login_w, $login_h) = (360, 210);
my $login = Win32::GUI::DialogBox->new(-name => 'Login',
-title => "$Net::OICQ::Win32Client::ModInfo login",
-size => [$login_w, $login_h]);
my ($x, $y) = (10, 20);
$login->AddLabel(-name => "QQLabel", -title => 'QQ:', -align => 'right',
-pos => [$x, $y], -size => [100, 20]);
$login->AddTextfield(-name => "QQ", -readonly => 0, -number => 1,
-pos => [$x + 100, $y], -size => [100, 20], -tabstop => 1);
$y += 30;
$login->AddLabel(-name => "PWLabel", -title => 'PW:', -align => 'right',
-pos => [$x, $y], -size => [100, 20]);
$login->AddTextfield(-name => "PW", -readonly => 0, -password => 1,
-pos => [$x + 100, $y], -size => [100, 20], -tabstop => 1);
$y += 30;
$login->AddLabel(-name => "InvLabel", -title => 'Invisible:', -align => 'right',
-pos => [$x, $y], -size => [100, 20]);
$login->AddCheckbox(-name => "Inv", -pos => [$x + 100, $y], -size => [10, 10], -tabstop => 1);
$login->AddLabel(-name => "UDLabel", -title => 'UDP:', -align => 'right',
-pos => [$x + 150, $y], -size => [50, 20]);
$login->AddCheckbox(-name => "UDP", -pos => [$x + 200, $y], -size => [10, 10], -tabstop => 1);
$y += 20;
$login->AddLabel(-name => "ProxyLabel", -title => 'Proxy:', -align => 'right',
-pos => [$x, $y], -size => [100, 20]);
$login->AddTextfield(-name => "Proxy", -readonly => 0,
-pos => [$x + 100, $y], -size => [150, 20], -tabstop => 1);
$y += 40;
$login->AddButton(-name => "LoginButton", -title => 'Login', -default => 1, -ok => 1,
-pos => [$x + 100, $y], -size => [40, 20], -tabstop => 1);
$login->AddButton(-name => "CancelButton", -title => 'Hide', -cancel => 1,
-pos => [$x + 200, $y], -size => [40, 20], -tabstop => 1);
$login->Center();
$login->{Inv}->Click();
$login->{QQ}->SetFocus();
$login->Show();
return $login;
}
sub UDP_Click {
if ($Login->{UDP}->Checked) {
$Login->{Proxy}->Disable;
} else {
$Login->{Proxy}->Enable;
}
return 1;
}
sub LoginButton_Click {
my $qq = $Login->{QQ}->GetLine(0);
my $pw = $Login->{PW}->GetLine(0);
if (defined($qq) and defined($pw) and $qq =~ /^\d{5,10}$/ and $pw) {
$QQ = $qq;
$PW = $pw;
$Mode = $Login->{Inv}->Checked() ? 'Invisible' : 'Normal';
if ($Login->{UDP}->Checked()) {
$Proto = 'udp';
$Proxy = '';
} else {
$Proto = 'tcp';
$Proxy = $Login->{Proxy}->GetLine(0);
$Proxy = '' unless defined $Proxy;
}
print "Mode: $Mode Proto: $Proto Proxy: $Proxy\n";
$Login->Hide();
return -1;
}
return 0;
}
sub CancelButton_Click {
$QQ = undef;
$PW = undef;
$Login->Hide;
return -1;
}
sub ChatCancelButton_Click {
my ($qq, $win) = get_active_chat_window();
my $chatwin = $GUI->{ChatWindow};
return 0 unless defined($qq) and exists($chatwin->{$qq});
$chatwin->{$qq} = undef;
delete($chatwin->{$qq});
$GUI->info("Chat window $qq deleted\r\n");
return 1;
}
sub Input_KeyUp {
my ($time, $key) = @_;
#print "$key\n";
return $GUI->text_input_handler($GUI->{Main}->{Input}, $key,
sub {
my ($gui, $text) = @_;
exit if $text =~ /^\s*(:?exit|quit)\s*$/;
if ($text =~ /^(\d{4,10})\s*$/) {
my $qq = $1;
my $win = $gui->get_chat_window($qq);
$win->Show() if defined $win;
return 1;
}
my $text2 = $text;
$text2 =~ s/^\s+//s;
$text2 =~ s/\s+$//s;
my ($cmd, @args) = split(/\s+/, $text2);
$cmd =~ s/\W//;
my $cmd_info = $gui->kb_cmd($cmd);
if (defined($cmd_info)) {
if (@args < $cmd_info->[1]) {
$gui->error("Not enough argument for command $cmd\r\n");
} else {
eval { $cmd_info->[0]->($gui, @args) };
$@ && $gui->error("$@");
}
} else {
my $output = $gui->{Main}->{Output};
$output->Append("eval $text\r\n");
my $res = eval $text;
$@ && $gui->error("$@\r\n");
if ($res) {$res =~ s/\n/\r\n/sg; $gui->info("$res\r\n")}
}
return 1;
});
}
sub ChatInput_KeyUp {
my ($time, $key) = @_;
return 1 unless $key == 0x0d;
my ($winid, $win) = get_active_chat_window();
return 1 unless defined $win;
my $input = $win->{ChatInput};
return $GUI->text_input_handler($input, $key,
sub {my ($gui, $text, $winid, $win) = @_;
my $oicq = $gui->{OICQ};
my $output = $win->{ChatOutput};
my $nick = $oicq->get_nickname($oicq->{Id});
$output->Append(substr(localtime, 11, 9) . "$nick\r\n$text\r\n");
if ($win->Text =~ /group/) {
$oicq->send_group_msg($winid, $text);
} else {
$oicq->send_msg($winid, $text);
}
return 1;
},
$winid, $win);
}
sub get_active_chat_window {
my $handle = Win32::GUI->GetActiveWindow();
my $chatwin = $GUI->{ChatWindow};
foreach my $winid (keys %$chatwin) {
if ($chatwin->{$winid}->{'-handle'} eq $handle) {
return ($winid, $chatwin->{$winid});
}
}
return;
}
sub Login_Terminate {
return -1;
}
sub Chat_Terminate {
ChatCancelButton_Click();
return 1;
}