summary refs log blame commit diff stats
path: root/plugins/perl/lib/Xchat/Embed.pm
blob: dffbaf5e49c0f344315f8a79a51821f9d957a748 (plain) (tree)




























































































































































































































































                                                                                          
package Xchat::Embed;
use strict;
use warnings;
# list of loaded scripts keyed by their package names
our %scripts;

sub load {
	my $file = expand_homedir( shift @_ );
	my $package = file2pkg( $file );
	
	if( exists $scripts{$package} ) {
		my $pkg_info = pkg_info( $package );
		my $filename = File::Basename::basename( $pkg_info->{filename} );
		Xchat::printf(
			qq{'%s' already loaded from '%s'.\n},
			$filename, $pkg_info->{filename}
		);
		Xchat::print(
			'If this is a different script then it rename and try '.
			'loading it again.'
		);
		return 2;
	}
	
	if( open my $source_handle, $file ) {
		my $source = do {local $/; <$source_handle>};
		close $source_handle;
		# we shouldn't care about things after __END__
		$source =~ s/^__END__.*//ms;
		
		if(
			my @replacements = $source =~
				m/^\s*package ((?:[^\W:]+(?:::)?)+)\s*?;/mg
		) {
			
			if ( @replacements > 1 ) {
				Xchat::print(
					"Too many package defintions, only 1 is allowed\n"
				);
				return 1;
			}
			
			my $original_package = shift @replacements;
			
			# remove original package declaration
			$source =~ s/^(package $original_package\s*;)/#$1/m;
			
			# fixes things up for code calling subs with fully qualified names
			$source =~ s/${original_package}:://g;
		}
		
		# this must come before the eval or the filename will not be found in
		# Xchat::register
		$scripts{$package}{filename} = $file;
		$scripts{$package}{loaded_at} = Time::HiRes::time();

		my $full_path = File::Spec->rel2abs( $file );
		$source =~ s/^/#line 1 "$full_path"\n\x7Bpackage $package;/;

		# make sure we add the closing } even if the last line is a comment
		if( $source =~ /^#.*\Z/m ) {
			$source =~ s/^(?=#.*\Z)/\x7D/m;
		} else {
			$source =~ s/\Z/\x7D/;
		}

		_do_eval( $source );

		unless( exists $scripts{$package}{gui_entry} ) {
			$scripts{$package}{gui_entry} =
				Xchat::Internal::register(
					"", "unknown", "", $file
				);
		}
		
		if( $@ ) {
			# something went wrong
			$@ =~ s/\(eval \d+\)/$file/g;
			Xchat::print( "Error loading '$file':\n$@\n" );
			# make sure the script list doesn't contain false information
			unload( $scripts{$package}{filename} );
			return 1;
		}
	} else {
		Xchat::print( "Error opening '$file': $!\n" );
		return 2;
	}

	return 0;
}

sub _do_eval {
	no strict;
	no warnings;
	eval $_[0];
}

sub unload {
	my $file = shift @_;
	my $package = file2pkg( $file );
	my $pkg_info = pkg_info( $package );

	if( $pkg_info ) {	
		# take care of the shutdown callback
		if( exists $pkg_info->{shutdown} ) {
			# allow incorrectly written scripts to be unloaded
			eval {
				if( ref $pkg_info->{shutdown} eq 'CODE' ) {
					$pkg_info->{shutdown}->();
				} elsif ( $pkg_info->{shutdown} ) {
					no strict 'refs';
					&{$pkg_info->{shutdown}};
				}
			};
		}

		if( exists $pkg_info->{hooks} ) {
			for my $hook ( @{$pkg_info->{hooks}} ) {
				Xchat::unhook( $hook, $package );
			}
		}


		if( exists $pkg_info->{gui_entry} ) {
			plugingui_remove( $pkg_info->{gui_entry} );
		}
		
		Symbol::delete_package( $package );
		delete $scripts{$package};
		return Xchat::EAT_ALL;
	} else {
		Xchat::print( qq{"$file" is not loaded.\n} );
		return Xchat::EAT_NONE;
	}
}

sub unload_all {
	for my $package ( keys %scripts ) {
		unload( $scripts{$package}->{filename} );
	}
	
	return Xchat::EAT_ALL;
}

sub reload {
	my $file = shift @_;
	my $package = file2pkg( $file );
	my $pkg_info = pkg_info( $package );
	my $fullpath = $file;
	
	if( $pkg_info ) {
		$fullpath = $pkg_info->{filename};
		unload( $file );
	}
	
	load( $fullpath );
	return Xchat::EAT_ALL;
}

sub reload_all {
	my @dirs = Xchat::get_info( "xchatdirfs" ) || Xchat::get_info( "xchatdir" );
	push @dirs, File::Spec->catdir( $dirs[0], "plugins" );
	for my $dir ( @dirs ) {
		my $auto_load_glob = File::Spec->catfile( $dir, "*.pl" );
		my @scripts = map { $_->{filename} }
			sort { $a->{loaded_at} <=> $b->{loaded_at} } values %scripts;
		push @scripts, File::Glob::bsd_glob( $auto_load_glob );

		my %seen;
		@scripts = grep { !$seen{ $_ }++ } @scripts;

		unload_all();
		for my $script ( @scripts ) {
			if( !pkg_info( file2pkg( $script ) ) ) {
				load( $script );
			}
		}
	}
}

sub expand_homedir {
	my $file = shift @_;

	if ( $^O eq "MSWin32" ) {
		$file =~ s/^~/$ENV{USERPROFILE}/;
	} else {
		$file =~ s{^~}{
			(getpwuid($>))[7] ||  $ENV{HOME} || $ENV{LOGDIR}
		}ex;
	}
	return $file;
}

sub file2pkg {
	my $string = File::Basename::basename( shift @_ );
	$string =~ s/\.pl$//i;
	$string =~ s|([^A-Za-z0-9/])|'_'.unpack("H*",$1)|eg;
	return "Xchat::Script::" . $string;
}

sub pkg_info {
	my $package = shift @_;
	return $scripts{$package};
}

sub find_external_pkg {
	my $level = 1;

	while( my @frame = caller( $level ) ) {
		return @frame if $frame[0] !~ /^Xchat/;
		$level++;
	}

}

sub find_pkg {
	my $level = 1;

	while( my ($package, $file, $line) = caller( $level ) ) {
		return $package if $package =~ /^Xchat::Script::/;
		$level++;
	}

	my @frame = find_external_pkg();
	my $location;

	if( $frame[0] or $frame[1] ) {
		$location = $frame[1] ? $frame[1] : "package $frame[0]";
		$location .= " line $frame[2]";
	} else {
		$location = "unknown location";
	}

	die "Unable to determine which script this hook belongs to. at $location\n";

}

sub fix_callback {
	my ($package, $callback) = @_;
	
	unless( ref $callback ) {
		# change the package to the correct one in case it was hardcoded
		$callback =~ s/^.*:://;
		$callback = qq[${package}::$callback];

		no strict 'subs';
		$callback = \&{$callback};
	}
	
	return $callback;
}

1
rvnot->lastoff = time (0); if (!quiet) EMIT_SIGNAL (XP_TE_NOTIFYOFFLINE, sess, nick, serv->servername, server_get_network (serv, TRUE), NULL, 0); fe_notify_update (nick); fe_notify_update (0); } static void notify_announce_online (server * serv, struct notify_per_server *servnot, char *nick) { session *sess; sess = serv->front_session; servnot->lastseen = time (0); if (servnot->ison) return; servnot->ison = TRUE; servnot->laston = time (0); EMIT_SIGNAL (XP_TE_NOTIFYONLINE, sess, nick, serv->servername, server_get_network (serv, TRUE), NULL, 0); fe_notify_update (nick); fe_notify_update (0); if (prefs.hex_notify_whois_online) { /* Let's do whois with idle time (like in /quote WHOIS %s %s) */ char *wii_str = malloc (strlen (nick) * 2 + 2); sprintf (wii_str, "%s %s", nick, nick); serv->p_whois (serv, wii_str); free (wii_str); } } /* handles numeric 601 */ void notify_set_offline (server * serv, char *nick, int quiet) { struct notify_per_server *servnot; servnot = notify_find (serv, nick); if (!servnot) return; notify_announce_offline (serv, servnot, nick, quiet); } /* handles numeric 604 and 600 */ void notify_set_online (server * serv, char *nick) { struct notify_per_server *servnot; servnot = notify_find (serv, nick); if (!servnot) return; notify_announce_online (serv, servnot, nick); } static void notify_watch (server * serv, char *nick, int add) { char tbuf[256]; snprintf (tbuf, sizeof (tbuf), "WATCH +%s", nick); if (!add) tbuf[6] = '-'; serv->p_raw (serv, tbuf); } static void notify_watch_all (struct notify *notify, int add) { server *serv; GSList *list = serv_list; while (list) { serv = list->data; if (serv->connected && serv->end_of_motd && serv->supports_watch && notify_do_network (notify, serv)) notify_watch (serv, notify->name, add); list = list->next; } } static void notify_flush_watches (server * serv, GSList *from, GSList *end) { char tbuf[512]; GSList *list; struct notify *notify; strcpy (tbuf, "WATCH"); list = from; while (list != end) { notify = list->data; strcat (tbuf, " +"); strcat (tbuf, notify->name); list = list->next; } serv->p_raw (serv, tbuf); } /* called when logging in. e.g. when End of motd. */ void notify_send_watches (server * serv) { struct notify *notify; GSList *list; GSList *point; int len; len = 0; point = list = notify_list; while (list) { notify = list->data; if (notify_do_network (notify, serv)) { len += strlen (notify->name) + 2 /* + and space */; if (len > 500) { notify_flush_watches (serv, point, list); len = strlen (notify->name) + 2; point = list; } } list = list->next; } if (point) notify_flush_watches (serv, point, NULL); } /* called when receiving a ISON 303 - should this func go? */ void notify_markonline (server *serv, char *word[]) { struct notify *notify; struct notify_per_server *servnot; GSList *list = notify_list; int i, seen; while (list) { notify = (struct notify *) list->data; servnot = notify_find_server_entry (notify, serv); if (!servnot) { list = list->next; continue; } i = 4; seen = FALSE; while (*word[i]) { if (!serv->p_cmp (notify->name, word[i])) { seen = TRUE; notify_announce_online (serv, servnot, notify->name); break; } i++; /* FIXME: word[] is only a 32 element array, limits notify list to about 27 people */ if (i > PDIWORDS - 5) { /*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/ break; } } if (!seen && servnot->ison) { notify_announce_offline (serv, servnot, notify->name, FALSE); } list = list->next; } fe_notify_update (0); } /* yuck! Old routine for ISON notify */ static void notify_checklist_for_server (server *serv) { char outbuf[512]; struct notify *notify; GSList *list = notify_list; int i = 0; strcpy (outbuf, "ISON "); while (list) { notify = list->data; if (notify_do_network (notify, serv)) { i++; strcat (outbuf, notify->name); strcat (outbuf, " "); if (strlen (outbuf) > 460) { /* LAME: we can't send more than 512 bytes to the server, but * * if we split it in two packets, our offline detection wouldn't * work */ /*fprintf (stderr, _("*** HEXCHAT WARNING: notify list too large.\n"));*/ break; } } list = list->next; } if (i) serv->p_raw (serv, outbuf); } int notify_checklist (void) /* check ISON list */ { struct server *serv; GSList *list = serv_list; while (list) { serv = list->data; if (serv->connected && serv->end_of_motd && !serv->supports_watch) { notify_checklist_for_server (serv); } list = list->next; } return 1; } void notify_showlist (struct session *sess) { char outbuf[256]; struct notify *notify; GSList *list = notify_list; struct notify_per_server *servnot; int i = 0; EMIT_SIGNAL (XP_TE_NOTIFYHEAD, sess, NULL, NULL, NULL, NULL, 0); while (list) { i++; notify = (struct notify *) list->data; servnot = notify_find_server_entry (notify, sess->server); if (servnot && servnot->ison) snprintf (outbuf, sizeof (outbuf), _(" %-20s online\n"), notify->name); else snprintf (outbuf, sizeof (outbuf), _(" %-20s offline\n"), notify->name); PrintText (sess, outbuf); list = list->next; } if (i) { sprintf (outbuf, "%d", i); EMIT_SIGNAL (XP_TE_NOTIFYNUMBER, sess, outbuf, NULL, NULL, NULL, 0); } else EMIT_SIGNAL (XP_TE_NOTIFYEMPTY, sess, NULL, NULL, NULL, NULL, 0); } int notify_deluser (char *name) { struct notify *notify; struct notify_per_server *servnot; GSList *list = notify_list; while (list) { notify = (struct notify *) list->data; if (!rfc_casecmp (notify->name, name)) { fe_notify_update (notify->name); /* Remove the records for each server */ while (notify->server_list) { servnot = (struct notify_per_server *) notify->server_list->data; notify->server_list = g_slist_remove (notify->server_list, servnot); free (servnot); } notify_list = g_slist_remove (notify_list, notify); notify_watch_all (notify, FALSE); if (notify->networks) free (notify->networks); free (notify->name); free (notify); fe_notify_update (0); return 1; } list = list->next; } return 0; } void notify_adduser (char *name, char *networks) { struct notify *notify = malloc (sizeof (struct notify)); if (notify) { memset (notify, 0, sizeof (struct notify)); if (strlen (name) >= NICKLEN) { notify->name = malloc (NICKLEN); safe_strcpy (notify->name, name, NICKLEN); } else { notify->name = strdup (name); } if (networks) notify->networks = despacify_dup (networks); notify->server_list = 0; notify_list = g_slist_prepend (notify_list, notify); notify_checklist (); fe_notify_update (notify->name); fe_notify_update (0); notify_watch_all (notify, TRUE); } } gboolean notify_is_in_list (server *serv, char *name) { struct notify *notify; GSList *list = notify_list; while (list) { notify = (struct notify *) list->data; if (!serv->p_cmp (notify->name, name)) return TRUE; list = list->next; } return FALSE; } int notify_isnotify (struct session *sess, char *name) { struct notify *notify; struct notify_per_server *servnot; GSList *list = notify_list; while (list) { notify = (struct notify *) list->data; if (!sess->server->p_cmp (notify->name, name)) { servnot = notify_find_server_entry (notify, sess->server); if (servnot && servnot->ison) return TRUE; } list = list->next; } return FALSE; } void notify_cleanup () { GSList *list = notify_list; GSList *nslist, *srvlist; struct notify *notify; struct notify_per_server *servnot; struct server *serv; int valid; while (list) { /* Traverse the list of notify structures */ notify = (struct notify *) list->data; nslist = notify->server_list; while (nslist) { /* Look at each per-server structure */ servnot = (struct notify_per_server *) nslist->data; /* Check the server is valid */ valid = FALSE; srvlist = serv_list; while (srvlist) { serv = (struct server *) srvlist->data; if (servnot->server == serv) { valid = serv->connected; /* Only valid if server is too */ break; } srvlist = srvlist->next; } if (!valid) { notify->server_list = g_slist_remove (notify->server_list, servnot); free (servnot); nslist = notify->server_list; } else { nslist = nslist->next; } } list = list->next; } fe_notify_update (0); }