All pastes #1733124 Raw Edit

Miscellany

public text v1 · immutable
#1733124 ·published 2009-12-31 15:43 UTC
rendered paste body
# /PROMCHANS <no parameters>
#
# List channels that are visited by the users from the active channel
#
# PURPOSE:
#	1. discover interesting channels, 
#	   e.g., partner and sub-channels, channels visited by people like you,
#	   overlooked channels on your topics
#	2. discover groups/milieus in the active channel, 
#	   e.g., developers, political parties, linux geeks; 
#	   @TODO why? identify the usual suspects?
#
# EXAMPLE OUTPUT:
#	Prominent channels in here:
#	56% 30 #ccc  20% 13 #chaosradio  7% 6 #haskell #c 5 #java #erlang  
#	4% 4 #26c3 3 #ak-vorrat  1% 2 #berlin #flirt #gentoo #openoffice #scribus
#
#	screenshot: 
#	http://img37.imageshack.us/img37/5782/screenshot2009122900034.png
#
# ISSUES:
#	- performs very slowly: 36 users->1 minute, 96->4, 300->11
#	- bombing an IRC server with hundreds of WHOIS requests isn't very ethical
#	  anyway: #freenode staff said, those can be answered with local 
#	  information (non-remote whois) and should be fairly cheap 
#	- starting a new /promchans while another one is running -> UB?
#	- doing simple /whois while /promchans is running -> UB?
#
# ASSUMES:
#	- there is a vast number of channels (24K on freenode)
#	- users (collaboratively) explore these channels over time 
#	  (55K-60K users on freenode)
#	- users usually don't stay in waste channels
#	-> every user's channel list (WHOIS) is a sort of recommendation: 
#	  "these channels are worth my time and attention"
#	- if several users visit the same channel, perhaps it's worth the visit
#	-> resilient "recommendations" emerge from aggregated channel lists:
#	  the more the better
#	- you're in a channel together with people who probably share your 
#	  interests or mindset
#	- they joined channels that might interest you too
#	-> their aggregated channel lists somehow fit to your interests
#		- the user composition in your channel is the bias here
#	=> serendipity
#	- diversity in the results: #language -> tools, libs, projects, 
#	  other languages, aspects (security, math, electronics), 
#	  culture (#26c3, #startups)
#	- neither the number of visitors nor the ratio is sufficient info:
#	  "75 users join #example is pretty much, even the majority here" (no, just 6%)
#	  "6% users join #example is moderate - maybe 8 users..." (no, 75!)
#		-> we need both values: "75 or 6% of all users join #example"
#	- ...
#	- a present WHOIS service
#	- a significant number of users not hiding their channel list
#
# REALITY:
#	- the most interesting channels were listed in the mid or lower range 
#	  of counted visitors; top range offers already known channels or 
#	  partner/sub-chans
#
# OPERATION BREAKDOWN:
# 	1. get nicks in active channel
# 	2. WHOIS every nick for his channel list
#	3. sum up number of visitors of each channel
#	4. sort channels by their number of visitors in descending order
# 	5. output channels
#
# TODO:
#	- rule out duplicate nicknames, e.g., nickname and nickname_
#	- improved error handling
#	- output estimated runtime as "at 20:43" instead of "in 5 minutes"
#	- estimate runtime on the basis of the actual performance 
#		if formular doesn't work for other
#	- dark grey falsely appears bold
#	- output number of users in every prominent channel
#	- output to status window instead of the active channel? (convenience)
#	- display percent /and/ number of visitors?
#	- add [#channel] or [#channels ...] parameter
#	- consider user's chat time?
# 


# Perl v5.8.8

use strict;
use vars qw($VERSION %IRSSI);

use Irssi;
use Irssi qw(command_bind signal_add signal_remove);

$VERSION = '0.9';
%IRSSI   = (authors     => '',
            contact     => '',
            url         => '',
            name        => 'Promchans',
            description => 'List channels that are visited by the users from the active channel',
            created     => '2009-12-10',
            license     => 'Public Domain',);


# globals (smells are OK with small irssi scripts)
{
	our %num_visitors;  # per channel name (key)
	our $my_channel;    # active Channel-object
	our $num_users;     # in active channel
	our $num_awaited_users = 0;  # burn down
}

# @param   Channel  object
# @return  bool     true if error
#
sub print_unsatisfied_preconditions
{
	my ($channel) = @_;

	if (! $channel 
	|| ref($channel) ne 'Irssi::Irc::Channel' 
	|| $channel->{'type'} ne 'CHANNEL')
	{
		print MSGS 'Call /PROMCHANS in a channel';
		return 1;
	}
	
	if (! $channel->{'names_got'})
	{
		print MSGS 'Wait until Irssi received all names, then retry';
		return 1;
	}

	return 0;
}

# @param   Channel  object
# @param   int      number of users
# @return  int
#
sub print_runtime_estimate
{
	my ($channel, $num_users) = @_;
	
	# runtime is O(n)
	# spreadsheet 'promchans.ods' computes slope and intercept from samples;
	#
	my $minutes = int(0.5 + (0.04 * $num_users) + 0.03);  # rounded

	$channel->print($minutes > 1 ? "Need $minutes minutes..." : "Wait...",
			MSGLEVEL_MSGS);
}

# @param   Channel  object
# @return  void
#
sub print_summary
{	
	my  ($channel) = @_;
	our %num_visitors;
	our $num_users;

	$channel->print('Prominent channels in here:', MSGLEVEL_MSGS);

	my $summary    = '';
	my $prev_num   = 0;
	my $prev_share = 0;

	# Sort by value in descending order
	foreach my $channel_name 
	(sort { $num_visitors{$b} <=> $num_visitors{$a} } keys %num_visitors)
	{
		my $num   = $num_visitors{$channel_name};
		my $share = int(0.5 + (($num / $num_users) * 100));  # rounded

		last if $share < 1;

		$summary .= " %9$share%%%9" if $share != $prev_share;
		$summary .= " $num "        if $num   != $prev_num;
		$summary .= $share > 1 ? '%c' : '%K';
		$summary .= "%c$channel_name%n ";
	
		$prev_num   = $num;
		$prev_share = $share;
	}

	$summary = '%9none%9' if !$summary;
	$channel->print($summary, MSGLEVEL_MSGS);
}

# @return  void
#
sub script_finished
{
	print_summary(our $my_channel);

	# restore original state
	signal_remove('redir user_channels_found', 'user_channels_found');
	signal_remove('redir done_with_user',      'done_with_user');
}

# @ingroup  irssi_signal
# @param    Server  object
# @param    string  channel names
# @return   void
#
sub user_channels_found  
{
	my  ($server, $args) = @_;
	our %num_visitors;
	our $my_channel;

	$_ = $args;
	my @channel_names = /\#[\#a-zA-Z0-9_-]*/g;
	my @relevant_channel_names = grep { $_ ne $my_channel->{name} && $_ ne '#' } @channel_names;  

	foreach my $channel_name (@relevant_channel_names)
	{
		$num_visitors{$channel_name}++;
	}
}

# @ingroup  irssi_signal
# @param    Server  object
# @return   void
#
sub done_with_user
{
	our $num_awaited_users--;
	script_finished() if $num_awaited_users == 0;
}

# @ingroup  irssi_command
# @return   void
#
sub prominent_channels_sought
{
	my  ($args, $server, $witem) = @_;
	our %num_visitors = ();
	our $my_channel   = Irssi::active_win->{active};

	return if print_unsatisfied_preconditions($my_channel);

	my  @usernames         = map { $_->{nick} } $my_channel->nicks();
	our $num_users         = @usernames;
	our $num_awaited_users = $num_users;

	print_runtime_estimate($my_channel, $num_awaited_users);

	signal_add('redir user_channels_found', 'user_channels_found');
	signal_add('redir done_with_user',      'done_with_user');

	foreach my $username (@usernames)
	{
		$server->redirect_event('whois', 1, $username, 0, undef, {
				# 319 Channels
				'event 319' => 'redir user_channels_found',
				# 318 End of WHOIS
				'event 318' => 'redir done_with_user',
				# 401 No such nickname
				'event 401' => 'redir done_with_user',
				# NOP
				'' => 'event empty',});
		
		$server->send_raw("WHOIS :$username");
	}
}


command_bind('promchans', 'prominent_channels_sought');