diff options
| author | wilk | 2017-03-11 21:22:02 +0100 |
|---|---|---|
| committer | GitHub | 2017-03-11 21:22:02 +0100 |
| commit | 7d56ba19ec8048a9135aab37aac0d337224ed38b (patch) | |
| tree | 029c3f0a6c34eb008c0087eb5cda7b90b4c23d87 /scripts | |
| parent | c8ccd65324fae283b0c7bf702c20a3893d3fa444 (diff) | |
| parent | 4102e04846a43d29a86dafc1cb99ef6aea7b81a5 (diff) | |
| download | scripts.irssi.org-7d56ba19ec8048a9135aab37aac0d337224ed38b.tar.bz2 | |
Merge pull request #2 from irssi/master
Resync with main repository.
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/adv_windowlist.pl | 8 | ||||
| -rw-r--r-- | scripts/aspell.pl | 7 | ||||
| -rw-r--r-- | scripts/cap_sasl_fail.pl | 6 | ||||
| -rw-r--r-- | scripts/chanact.pl | 181 | ||||
| -rw-r--r-- | scripts/colorkick.pl | 15 | ||||
| -rw-r--r-- | scripts/ctrlact.pl | 574 | ||||
| -rw-r--r-- | scripts/dccself.pl | 3 | ||||
| -rw-r--r-- | scripts/desktop-notify.pl | 13 | ||||
| -rw-r--r-- | scripts/go.pl | 54 | ||||
| -rw-r--r-- | scripts/hilightwin.pl | 26 | ||||
| -rw-r--r-- | scripts/hilite_url.pl | 28 | ||||
| -rw-r--r-- | scripts/ident.pl | 68 | ||||
| -rw-r--r-- | scripts/invitejoin.pl | 263 | ||||
| -rw-r--r-- | scripts/iquiz.pl | 1261 | ||||
| -rw-r--r-- | scripts/iquiz_en.pl | 1261 | ||||
| -rw-r--r-- | scripts/listsort.pl | 60 | ||||
| -rw-r--r-- | scripts/logcompress_perl.pl | 5 | ||||
| -rw-r--r-- | scripts/mh_sbuserinfo.pl | 162 | ||||
| -rw-r--r-- | scripts/nickserv.pl | 502 | ||||
| -rw-r--r-- | scripts/pager.pl | 9 | ||||
| -rw-r--r-- | scripts/perlalias.pl | 288 | ||||
| -rw-r--r-- | scripts/postpone.pl | 8 | ||||
| -rw-r--r-- | scripts/print_signals.pl | 280 | ||||
| -rw-r--r-- | scripts/reorder.pl | 311 | ||||
| -rw-r--r-- | scripts/rud_emotes.pl | 58 | ||||
| -rw-r--r-- | scripts/trackbar22.pl | 11 | ||||
| -rw-r--r-- | scripts/translit.pl | 3 | ||||
| -rw-r--r-- | scripts/xdcc_autoget.pl | 16 |
28 files changed, 4945 insertions, 536 deletions
diff --git a/scripts/adv_windowlist.pl b/scripts/adv_windowlist.pl index ee0219b..6285786 100644 --- a/scripts/adv_windowlist.pl +++ b/scripts/adv_windowlist.pl @@ -1,7 +1,7 @@ use strict; use warnings; -our $VERSION = '1.2'; # 762850b0c2c1d5a +our $VERSION = '1.3'; # 463402cffae35e5 our %IRSSI = ( authors => 'Nei', contact => 'Nei @ anti@conference.jabber.teamidiot.de', @@ -1710,8 +1710,13 @@ sub string_LCSS { (sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0] } +# workaround for issue #271 { package Irssi::Nick } +# workaround for issue #572 +@Irssi::UI::Exec::ISA = 'Irssi::Windowitem' + if Irssi::version >= 20140822 && Irssi::version <= 20161101 && !@Irssi::UI::Exec::ISA; + UNITCHECK { package AwlViewer; use strict; @@ -2388,6 +2393,7 @@ UNITCHECK # Changelog # ========= +# 1.3 - workaround for irssi issue #572 # 1.2 - new format to choose abbreviation character # 1.1 - infinite loop on shortening certain window names reported by Kalan # diff --git a/scripts/aspell.pl b/scripts/aspell.pl index b6a254e..71369f3 100644 --- a/scripts/aspell.pl +++ b/scripts/aspell.pl @@ -85,6 +85,7 @@ See README file. use warnings; use strict; use Data::Dumper; +use Encode 'decode'; use Irssi; use Irssi::Irc; use Irssi::TextUI; @@ -107,7 +108,7 @@ if ($@ && $@ =~ m/Can't locate/) { } -our $VERSION = '1.6.1'; +our $VERSION = '1.6.2'; our %IRSSI = ( authors => 'Isaac Good (yitz_), Tom Feist (shabble)', contact => 'irssi@isaacgood.com, shabble+irssi@metavore.org', @@ -306,6 +307,7 @@ sub process_word { } else { print_suggestions(); + highlight_incorrect_word($word_obj); } } else { @@ -391,7 +393,8 @@ sub spellcheck_finish { # stick the cursor at the end of the input line? my $input = _input(); - my $end = length($input); + my $charset = lc Irssi::settings_get_str('term_charset'); + my $end = length(decode $charset=>$input); Irssi::gui_input_set_pos($end); } diff --git a/scripts/cap_sasl_fail.pl b/scripts/cap_sasl_fail.pl index 226bc2c..22d0b79 100644 --- a/scripts/cap_sasl_fail.pl +++ b/scripts/cap_sasl_fail.pl @@ -1,7 +1,7 @@ use strict; use warnings; -our $VERSION = '2.0'; # ed9e98e5d63cfb3 +our $VERSION = '2.1'; # bb62357c61d9e54 our %IRSSI = ( authors => 'Nei', name => 'cap_sasl_fail', @@ -15,8 +15,8 @@ use version; my %disconnect_next; my $irssi_version = qv(Irssi::parse_special('v$J') =~ s/-.*//r); -die sprintf "Support for Irssi v%vd has not been written yet.\n", $irssi_version - if $irssi_version > v0.8.20; +die sprintf "Please use /set sasl_disconnect_on_failure instead of this script.\n" + if $irssi_version >= v1.0.0; Irssi::signal_register({'server sasl fail' => [qw[iobject string]]}); Irssi::signal_add_first('server sasl fail' => 'sasl_fail_failed'); diff --git a/scripts/chanact.pl b/scripts/chanact.pl index ee6ef3c..4a42364 100644 --- a/scripts/chanact.pl +++ b/scripts/chanact.pl @@ -1,20 +1,25 @@ use Irssi 20020101.0001 (); + use strict; -# FIXME use warning; +use warnings; + use Irssi::TextUI; use vars qw($VERSION %IRSSI); -$VERSION = "0.5.15"; +$VERSION = "0.6.0"; %IRSSI = ( - authors => 'BC-bd, Veli', - contact => 'bd@bc-bd.org, veli@piipiip.net', + authors => 'BC-bd', + contact => 'bd@bc-bd.org', name => 'chanact', description => 'Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias). Lets you give alias characters to windows so that you can select those with meta-<char>', license => 'GNU GPLv2 or later', - url => 'https://bc-bd.org/svn/repos/irssi/chanact' + url => 'http://bc-bd.org/blog/irssi/' ); +# Please send patches / pull requests to the email listed unter contact above +# and not to the irssi/scripts.irssi.org repository on github. + # Adds new powerful and customizable [Act: ...] item (chanelnames,modes,alias). # Lets you give alias characters to windows so that you can select those with # meta-<char>. @@ -27,19 +32,22 @@ $VERSION = "0.5.15"; # Contributors ######### # -# veli@piipiip.net /window_alias code -# qrczak@knm.org.pl chanact_abbreviate_names -# qerub@home.se Extra chanact_show_mode and chanact_chop_status +# veli@piipiip.net original /window_alias code +# qrczak@knm.org.pl chanact_abbreviate_names +# qerub@home.se Extra chanact_show_mode and chanact_chop_status # madduck@madduck.net Better channel aliasing (case-sensitive, cross-network) # chanact_filter_windowlist basis -# Jan 'jast' Krueger <jast@heapsort.de>, 2004-06-22 -# Ivo Timmermans <ivo@o2w.nl> win->{hilight} patch -# Trevor 'tee' Slocum <tslocum@gmail.com> Case-insensitive aliases, bugfix -# +# jast@heapsort.de Updated documentation +# ivo@o2w.nl win->{hilight} patch +# Bazerka base patch for sorting by level change +# updated documentation +# mrtnpaolo@gmail.com rename commands +# tslocum@gmail.com Case-insensitive aliases +# ######### # USAGE ### -# +# # copy the script to ~/.irssi/scripts/ # # In irssi: @@ -61,11 +69,11 @@ $VERSION = "0.5.15"; # aliases to your windows. Go to the window you want to give the alias to # and say: # -# /window_alias <alias char> +# /chanact_window_alias <alias char> # # You can remove the aliases with from an aliased window: # -# /window_unalias +# /chanact_window_unalias # # To see a list of your windows use: # @@ -100,11 +108,11 @@ $VERSION = "0.5.15"; # $H : Start highlightning # $S : Stop highlightning # * example: -# +# # /set chanact_display $H$N:$M.$S$C -# +# # will give you on #irssi.de if you have voice -# +# # [3:+.#irssi.de] # # with '3:+.' highlighted and the channel name printed in regular color @@ -113,12 +121,26 @@ $VERSION = "0.5.15"; # * ON : Aliases are case-sensitive # * OFF : Aliases are case-insensitive # -# Existing aliases must be reapplied after switching to case-insensitive. +# Existing aliases must be reapplied after changing this option +# +# Switching from OFF to ON _after_ aliases have been defined, and +# then redefining or changing an existing alias will leave some +# bindings behind, e.g. +# +# /set chanact_case_sensitive OFF +# /chanact_window_alias x +# +# -> window reachable with meta-x/meta-X +# +# /set chanact_case_sensitive ON +# /chanact_window_alias y +# +# -> window reachable with meta-y/meta-X # # /set chanact_display_alias <string> # as 'chanact_display' but is used if the window has an alias and # 'chanact_show_alias' is set to on. -# +# # /set chanact_show_names <ON|OFF> # * ON : show the channelnames after the number/alias # * OFF : don't show the names @@ -141,7 +163,7 @@ $VERSION = "0.5.15"; # # /set chanact_autorenumber <ON|OFF> # * ON : Move the window automatically to first available slot -# starting from "chanact_renumber_start" when assigning +# starting from "chanact_renumber_start" when assigning # an alias to window. Also moves the window back to a # first available slot from refnum 1 when the window # loses it's alias. @@ -160,7 +182,7 @@ $VERSION = "0.5.15"; # beginning of the channel name. # * example : # To shorten a lot of debian channels: -# +# # /set chanact_remove_prefix deb(ian.(devel-)?)? # # /set chanact_filter <int> @@ -174,7 +196,7 @@ $VERSION = "0.5.15"; # * <string> : space-separated list of windows for which to use # chanact_filter_windowlist_level instead of # chanact_filter. -# +# # Alternatively, an entry can be postfixed with # a comma (',') and the level to use for that # window. @@ -232,15 +254,14 @@ sub expand { # but we dont need to recreate the item every time so we first # check if something has changed and only then we recreate the string # this might just save some cycles -# FIXME implement $get_size_only check, and user $item->{min|max-size} sub chanact { my ($item, $get_size_only) = @_; if ($needRemake) { remake(); } - - $item->default_handler($get_size_only, $actString, undef, 1); + + $item->default_handler($get_size_only, $actString, "", 1); } # build a hash to easily access special levels based on @@ -268,13 +289,13 @@ sub calculate_levels(@) { my %levels; foreach my $win (@windows) { - # FIXME we could use the next statements to weed out entries in - # @windows that we will not need later on !ref($win) && next; my $name = $win->get_active_name; + # skip nameless windows + next unless $name; - if (exists($matches{$name})) { + if ($name && exists($matches{$name})) { $levels{$name} = $matches{$name}; } else { $levels{$name} = $default; @@ -293,7 +314,7 @@ sub calculate_levels(@) { # this is the real creation method sub remake() { my ($afternumber,$finish,$hilight,$mode,$number,$display,@windows); - my $separator = Irssi::settings_get_str('chanact_separator'); + my $separator = Irssi::settings_get_str('chanact_separator'); my $abbrev = Irssi::settings_get_int('chanact_abbreviate_names'); my $remove_prefix = Irssi::settings_get_str('chanact_remove_prefix'); my $remove_hash = Irssi::settings_get_bool('chanact_remove_hash'); @@ -317,6 +338,8 @@ sub remake() { $type = $active->{type} if $active; my $name = $win->get_active_name; + # skip windows without a name + next unless $name; my $filter_level = $type eq 'QUERY' ? $levels{'@QUERIES'} : $levels{$name}; @@ -334,7 +357,7 @@ sub remake() { && $name eq "(status)") { $name = "S"; } - + # check if we should show the mode $mode = ""; if ($type eq "CHANNEL") { @@ -346,7 +369,7 @@ sub remake() { my $nick = $channel->nick_find($server->{nick}); !ref($nick) && next; - + if ($nick->{op}) { $mode = "@"; } elsif ($nick->{voice}) { @@ -377,13 +400,13 @@ sub remake() { $name =~ s/^[&#+!=]//; } - if (Irssi::settings_get_bool('chanact_show_alias') == 1 && + if (Irssi::settings_get_bool('chanact_show_alias') == 1 && $win->{name} =~ /^([a-zA-Z+]):(.+)$/) { $number = "$1"; - $display = Irssi::settings_get_str('chanact_display_alias'); + $display = Irssi::settings_get_str('chanact_display_alias'); } else { $number = $win->{refnum}; - $display = Irssi::settings_get_str('chanact_display'); + $display = Irssi::settings_get_str('chanact_display'); } # fixup { and } in nicks, those are used by irssi themes @@ -396,7 +419,7 @@ sub remake() { if ($actString ne "") { # Remove the last separator $actString =~ s/$separator$//; - + $actString = "{sb ".Irssi::settings_get_str('chanact_header').$actString."}"; } @@ -429,12 +452,33 @@ sub setup_changed { chanactHasChanged(); } +# Remove key binding for current window +sub unbind { + my ($name, $server) = @_; + + # chanact'ified windows have a name like this: X:servertag/name. if we + # can't find anything like this we return and do not unbind nor renumber + # anything + my ($key, $tag) = split(/:/, $name); + return unless $tag; + + ($tag, $name) = split('/', $tag); + return unless (length($key) == 1); + + if (Irssi::settings_get_bool('chanact_case_sensitive')) { + $server->command("/bind -delete meta-$key"); + } else { + $server->command("/bind -delete meta-" . lc($key)); + $server->command("/bind -delete meta-" . uc($key)); + } +} + # Remove alias sub cmd_window_unalias { - my ($data, $server, $witem, $internal) = @_; + my ($data, $server, $witem) = @_; - if ($data ne '' && !$internal) { - Irssi::print("chanact: /window_unalias does not take any ". + if ($data ne '') { + Irssi::print("chanact: /chanact_window_unalias does not take any ". "parameters, Run it in the window you want to unalias"); return; } @@ -442,24 +486,7 @@ sub cmd_window_unalias { my $win = Irssi::active_win(); my $name = Irssi::active_win()->{name}; - # chanact'ified windows have a name like this: X:servertag/name - my ($key, $tag) = split(/:/, $name); - ($tag, $name) = split('/', $tag); - - # remove alias only of we have a single character keybinding, if we - # haven't the name was not set by chanact, so we won't blindly unset - # stuff - if (length($key) == 1) { - if (Irssi::settings_get_bool('chanact_case_sensitive')) { - $server->command("/bind -delete meta-$data"); - } else { - $server->command("/bind -delete meta-" . lc($data)); - $server->command("/bind -delete meta-" . uc($data)); - } - } elsif (!$internal) { - Irssi::print("chanact: could not determine keybinding. ". - "Won't unbind anything"); - } + unbind($name, $server); # set the windowname back to it's old one. We don't bother checking # for a vaild name here, as we want to remove the current one and if @@ -472,7 +499,7 @@ sub cmd_window_unalias { # we are renumbering, so move the window to the lowest available # refnum. my $refnum = 1; - while (Irssi::window_find_refnum($refnum) ne "") { + while (Irssi::window_find_refnum($refnum)) { $refnum++; } @@ -480,14 +507,13 @@ sub cmd_window_unalias { Irssi::print("chanact: moved wintow to refnum $refnum"); } -# function by veli@piipiip.net # Make an alias sub cmd_window_alias { my ($data, $server, $witem) = @_; my $rn_start = Irssi::settings_get_int('chanact_renumber_start'); unless ($data =~ /^[a-zA-Z+]$/) { - Irssi::print("Usage: /window_alias <char>"); + Irssi::print("Usage: /chanact_window_alias <char>"); return; } @@ -508,23 +534,23 @@ sub cmd_window_alias { $winname = $window->{name}; } - cmd_window_unalias($data, $server, $witem, 1); + unbind($window->{name}, $server); my $winnum = $window->{refnum}; - + if (Irssi::settings_get_bool('chanact_autorenumber') == 1 && $window->{refnum} < $rn_start) { my $old_refnum = $window->{refnum}; $winnum = $rn_start; - + # Find the first available slot and move the window - while (Irssi::window_find_refnum($winnum) ne "") { $winnum++; } + while (Irssi::window_find_refnum($winnum)) { $winnum++; } $window->set_refnum($winnum); - + Irssi::print("Moved the window from $old_refnum to $winnum"); } - + my $winserver = $window->{active_server}->{tag}; my $winhandle = "$winserver/$winname"; # cmd_window_unalias relies on a certain format here @@ -543,8 +569,8 @@ sub cmd_window_alias { $needRemake = 1; # Window alias command -Irssi::command_bind('window_alias','cmd_window_alias'); -Irssi::command_bind('window_unalias','cmd_window_unalias'); +Irssi::command_bind('chanact_window_alias','cmd_window_alias'); +Irssi::command_bind('chanact_window_unalias','cmd_window_unalias'); # our config item Irssi::settings_add_str('chanact', 'chanact_display', '$H$N:$M$C$S'); @@ -587,8 +613,15 @@ Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # # Changelog # -# 0.5.15 -# - fixed unbind error when aliasing a previously un-aliased window +# 0.6.0 +# - fixed URL +# - now with 'use warnings' +# - fix cmd_window_unalias call from cmd_window_alias +# - fix Use of uninitialized value $name in hash element warnings +# - return from cmd_window_unalias if the window has no valid +# chanact'ified name +# - rename /window_(un)alias to /chanact_window_(un)alias +# - fix refnum renumber race # - added setting to allow case-insensitive window aliases # # 0.5.14 @@ -645,10 +678,10 @@ Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # # 0.5.6 # - fixed a bug (#1) reported by Wouter Coekaert -# +# # 0.5.5 # - some speedups from David Leadbeater <dgl@dgl.cx> -# +# # # 0.5.4 # - added help for chanact_display_alias @@ -662,7 +695,7 @@ Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # - removed unused chanact_show_name settings (thx to Qerub) # - fixed $mode display # - guarded reference operations to (hopefully) fix errors on server disconnect -# +# # 0.5.1 # - small typo fixed # @@ -670,7 +703,7 @@ Irssi::signal_add('nick mode changed', 'chanactHasChanged'); # - changed chanact_show_mode to chanact_display. reversed changes from # Qerub through that, but kept funcionality. # - removed chanact_color_all since it is no longer needed -# +# # 0.4.3 # - changes by Qerub # + added chanact_show_mode to show the mode just before the channel name diff --git a/scripts/colorkick.pl b/scripts/colorkick.pl index 24b29fd..c28e420 100644 --- a/scripts/colorkick.pl +++ b/scripts/colorkick.pl @@ -13,7 +13,8 @@ use strict; use Irssi; use Irssi::Irc; -use vars %IRSSI; +use vars qw/%IRSSI $VERSION/; +$VERSION='0.1'; %IRSSI = ( authors => "Gabor Nyeki", @@ -22,7 +23,7 @@ use vars %IRSSI; description => "kicking users for using colors or blinks", license => "public domain", written => "Thu Dec 26 00:22:54 CET 2002", - changed => "Fri Jan 2 03:43:10 CET 2004" + changed => "2017-03-07" ); sub catch_junk @@ -31,15 +32,15 @@ sub catch_junk my ($target, $text) = split(/ :/, $data, 2); my $valid_channel = 0; - if ($target[0] != '#' && $target[0] != '!' && $target[0] != '&') - { - return; - } + #if ($target[0] != '#' && $target[0] != '!' && $target[0] != '&') + #{ + # return; + #} for my $channel (split(/ /, Irssi::settings_get_str('colorkick_channels'))) { - if ($target == $channel) + if ($target eq $channel) { $valid_channel = 1; last; diff --git a/scripts/ctrlact.pl b/scripts/ctrlact.pl new file mode 100644 index 0000000..da39804 --- /dev/null +++ b/scripts/ctrlact.pl @@ -0,0 +1,574 @@ +# ctrlact.pl — Irssi script for fine-grained control of activity indication +# +# © 2017 martin f. krafft <madduck@madduck.net> +# Released under the MIT licence. +# +### Usage: +# +# /script load ctrlact +# +# If you like a busy activity statusbar, this script is not for you. +# +# If, on the other hand, you don't care about most activity, but you do want +# the ability to define per-item and per-window, what level of activity should +# trigger a change in the statusbar, then ctrlact might be for you. +# +# For instance, you might never want to be disturbed by activity in any +# channel, unless someone highlights you. However, you do want all activity +# in queries (except on efnet, as well as an indication about any chatter in +# your company channels. The following ctrlact map would do this for you: +# +# channel /^#myco-/ messages +# channel * hilights +# query efnet * messages +# query * all +# +# These three lines would be interpreted/read as: +# "only messages or higher in a channel matching /^#myco-/ should trigger act" +# "in all other channels, only hilights (or higher) should trigger act" +# "queries on efnet should only trigger act for messages and higher" +# "messages of all levels should trigger act in queries elsewhere" +# +# The activity level in the third column is thus to be interpreted as +# "the minimum level of activity that will trigger an indication" +# +# Loading this script per-se should not change anything, except it will create +# ~/.irssi/ctrlact with some informational content, including the defaults and +# some examples. +# +# The four activity levels are, and you can use either the words, or the +# integers in the map. +# +# all (data_level: 1) +# messages (data_level: 2) +# hilights (data_level: 3) +# none (data_level: 4) +# +# Note that the name is either matched in full and verbatim, or treated like +# a regular expression, if it starts and ends with the same punctuation +# character. The asterisk ('*') is special and simply gets translated to /.*/ +# internally. No other wildcards are supported. +# +# Once you defined your mappings, please don't forget to /ctrlact reload them. +# You can then use the following commands from Irssi to check out the result: +# +# # list all mappings +# /ctrlact list +# +# # query the applicable activity levels, possibly limited to +# # windows/channels/queries +# /ctrlact query name [name, …] [-window|-channel|-query] +# +# # display the applicable level for each window/channel/query +# /ctrlact show [-window|-channel|-query] +# +# There's an interplay between window items and windows here, and you can +# specify mininum activity levels for each. Here are the rules: +# +# 1. if the minimum activity level of a window item (channel or query) is not +# reached, then the window is prevented from indicating activity. +# 2. if traffic in a window item does reach minimum activity level, then the +# minimum activity level of the window is considered, and activity is only +# indicated if the window's minimum activity level is lower. +# +# In general, this means you'd have windows defaulting to 'all', but it might +# come in handy to move window items to windows with min.levels of 'hilights' +# or even 'none' in certain cases, to further limit activity indication for +# them. +# +# You can use the Irssi settings activity_msg_level and activity_hilight_level +# to specify which IRC levels will be considered messages and hilights. Note +# that if an activity indication is inhibited, then there also won't be +# a beep (cf. beep_msg_level), unless you toggle ctrlmap_inhibit_beep. +# +### Settings: +# +# /set ctrlact_map_file [~/.irssi/ctrlact] +# Controls where the activity control map will be read from (and saved to) +# +# /set ctrlact_fallback_(channel|query|window)_threshold [1] +# Controls the lowest data level that will trigger activity for channels, +# queries, and windows respectively, if no applicable mapping could be +# found. +# +# /set ctrlact_inhibit_beep [on] +# If an activity wouldn't be indicated, also inhibit the beep/bell. Turn +# this off if you want the bell anyway. +# +# /set ctrlact_debug [off] +# Turns on debug output. Not that this may itself be buggy, so please don't +# use it unless you really need it. +# +### To-do: +# +# - figure out interplay with activity_hide_level +# - /ctrlact add/delete/move and /ctrlact save, maybe +# - completion for commands +# +use strict; +use warnings; +use Carp qw( croak ); +use Irssi; +use Text::ParseWords; + +our $VERSION = '1.2'; + +our %IRSSI = ( + authors => 'martin f. krafft', + contact => 'madduck@madduck.net', + name => 'ctrlact', + description => 'allows per-channel control over activity indication', + license => 'MIT', + changed => '2017-02-24' +); + +### DEFAULTS AND SETTINGS ###################################################### + +my $debug = 0; +my $map_file = Irssi::get_irssi_dir()."/ctrlact"; +my $fallback_channel_threshold = 1; +my $fallback_query_threshold = 1; +my $fallback_window_threshold = 1; +my $inhibit_beep = 1; + +Irssi::settings_add_str('ctrlact', 'ctrlact_map_file', $map_file); +Irssi::settings_add_bool('ctrlact', 'ctrlact_debug', $debug); +Irssi::settings_add_int('ctrlact', 'ctrlact_fallback_channel_threshold', $fallback_channel_threshold); +Irssi::settings_add_int('ctrlact', 'ctrlact_fallback_query_threshold', $fallback_query_threshold); +Irssi::settings_add_int('ctrlact', 'ctrlact_fallback_window_threshold', $fallback_window_threshold); +Irssi::settings_add_bool('ctrlact', 'ctrlact_inhibit_beep', $inhibit_beep); + +sub sig_setup_changed { + $debug = Irssi::settings_get_bool('ctrlact_debug'); + $map_file = Irssi::settings_get_str('ctrlact_map_file'); + $fallback_channel_threshold = Irssi::settings_get_int('ctrlact_fallback_channel_threshold'); + $fallback_query_threshold = Irssi::settings_get_int('ctrlact_fallback_query_threshold'); + $fallback_window_threshold = Irssi::settings_get_int('ctrlact_fallback_window_threshold'); + $inhibit_beep = Irssi::settings_get_bool('ctrlact_inhibit_beep'); +} +Irssi::signal_add('setup changed', \&sig_setup_changed); +Irssi::signal_add('setup reread', \&sig_setup_changed); +sig_setup_changed(); + +my $changed_since_last_save = 0; + +my @DATALEVEL_KEYWORDS = ('all', 'messages', 'hilights', 'none'); + +### HELPERS #################################################################### + +my $_inhibit_debug_activity = 0; +use constant DEBUGEVENTFORMAT => "%7s %7.7s %-22.22s %d %s %d → %-7s (%-8s ← %s)"; +sub debugprint { + return unless $debug; + my ($msg, @rest) = @_; + $_inhibit_debug_activity = 1; + Irssi::print("ctrlact debug: ".$msg, MSGLEVEL_CRAP); + $_inhibit_debug_activity = 0; +} + +sub error { + my ($msg) = @_; + Irssi::print("ctrlact: ERROR: $msg", MSGLEVEL_CLIENTERROR); +} + +my @window_thresholds; +my @channel_thresholds; +my @query_thresholds; + +sub match { + my ($pat, $text) = @_; + my $npat = ($pat eq '*') ? '/.*/' : $pat; + if ($npat =~ m/^(\W)(.+)\1$/) { + my $re = qr/$2/; + $pat = $2 unless $pat eq '*'; + return $pat if $text =~ /$re/i; + } + else { + return $pat if lc($text) eq lc($npat); + } + return 0; +} + +sub to_data_level { + my ($kw) = @_; + return $1 if $kw =~ m/^(\d+)$/; + foreach my $i (2..4) { + my $matcher = qr/^$DATALEVEL_KEYWORDS[5-$i]$/; + return 6-$i if $kw =~ m/$matcher/i; + } + return 1; +} + +sub from_data_level { + my ($dl) = @_; + croak "Invalid numeric data level: $dl" unless $dl =~ m/^([1-4])$/; + return $DATALEVEL_KEYWORDS[$dl-1]; +} + +sub walk_match_array { + my ($name, $net, $type, @arr) = @_; + foreach my $quadruplet (@arr) { + my $netmatch = $net eq '*' ? '(ignored)' + : match($quadruplet->[0], $net); + my $match = match($quadruplet->[1], $name); + next unless $netmatch and $match; + + my $result = to_data_level($quadruplet->[2]); + my $tresult = from_data_level($result); + $name = '(unnamed)' unless length $name; + $match = sprintf('line %3d = net:%s name:%s', + $quadruplet->[3], $netmatch, $match); + return ($result, $tresult, $match) + } + return -1; +} + +sub get_mappings_table { + my (@arr) = @_; + my @ret = (); + for (my $i = 0; $i < @arr; $i++) { + push @ret, sprintf("%4d: %-10s %-40s %-10s (line: %3d)", + $i, $arr[$i]->[0], $arr[$i]->[1], $arr[$i]->[2], $arr[$i]->[3]); + } + return join("\n", @ret); +} + +sub get_specific_threshold { + my ($type, $name, $net) = @_; + $type = lc($type); + if ($type eq 'window') { + return walk_match_array($name, $net, $type, @window_thresholds); + } + elsif ($type eq 'channel') { + return walk_match_array($name, $net, $type, @channel_thresholds); + } + elsif ($type eq 'query') { + return walk_match_array($name, $net, $type, @query_thresholds); + } + else { + croak "ctrlact: can't look up threshold for type: $type"; + } +} + +sub get_item_threshold { + my ($chattype, $type, $name, $net) = @_; + my ($ret, $tret, $match) = get_specific_threshold($type, $name, $net); + return ($ret, $tret, $match) if $ret > 0; + if ($type eq 'CHANNEL') { + return ($fallback_channel_threshold, from_data_level($fallback_channel_threshold), '[default]'); + } + else { + return ($fallback_query_threshold, from_data_level($fallback_query_threshold), '[default]'); + } +} + +sub get_win_threshold { + my ($name, $net) = @_; + my ($ret, $tret, $match) = get_specific_threshold('window', $name, $net); + if ($ret > 0) { + return ($ret, $tret, $match); + } + else { + return ($fallback_window_threshold, from_data_level($fallback_window_threshold), '[default]'); + } +} + +sub print_levels_for_all { + my ($type, @arr) = @_; + Irssi::print("ctrlact: $type mappings:"); + for (my $i = 0; $i < @arr; $i++) { + my $name = $arr[$i]->{'name'}; + my $net = $arr[$i]->{'server'}->{'tag'} // ''; + my ($t, $tt, $match) = get_specific_threshold($type, $name, $net); + my $c = ($type eq 'window') ? $arr[$i]->{'refnum'} : $arr[$i]->window()->{'refnum'}; + Irssi::print(sprintf("%4d: %-40.40s → %d (%-8s) match %s", $c, $name, $t, $tt, $match), MSGLEVEL_CRAP); + } +} + +### HILIGHT SIGNAL HANDLERS #################################################### + +my $_inhibit_beep = 0; +my $_inhibit_window = 0; + +sub maybe_inhibit_witem_hilight { + my ($witem, $oldlevel) = @_; + return unless $witem; + $oldlevel = 0 unless $oldlevel; + my $newlevel = $witem->{'data_level'}; + return if ($newlevel <= $oldlevel); + + $_inhibit_window = 0; + $_inhibit_beep = 0; + my $wichattype = $witem->{'chat_type'}; + my $witype = $witem->{'type'}; + my $winame = $witem->{'name'}; + my $witag = $witem->{'server'}->{'tag'} // ''; + my ($th, $tth, $match) = get_item_threshold($wichattype, $witype, $winame, $witag); + my $inhibit = $newlevel > 0 && $newlevel < $th; + debugprint(sprintf(DEBUGEVENTFORMAT, lc($witype), $witag, $winame, $newlevel, + $inhibit ? ('<',$th,'inhibit'):('≥',$th,'pass'), + $tth, $match)); + if ($inhibit) { + Irssi::signal_stop(); + # the rhval comes from config, so if the user doesn't want the + # bell inhibited, this is effectively a noop. + $_inhibit_beep = $inhibit_beep; + $_inhibit_window = $witem->window(); + } +} +Irssi::signal_add_first('window item hilight', \&maybe_inhibit_witem_hilight); + +sub inhibit_win_hilight { + my ($win) = @_; + Irssi::signal_stop(); + Irssi::signal_emit('window dehilight', $win); +} + +sub maybe_inhibit_win_hilight { + my ($win, $oldlevel) = @_; + return unless $win; + if ($_inhibit_debug_activity) { + inhibit_win_hilight($win); + } + elsif ($_inhibit_window && $win->{'refnum'} == $_inhibit_window->{'refnum'}) { + inhibit_win_hilight($win); + } + else { + $oldlevel = 0 unless $oldlevel; + my $newlevel = $win->{'data_level'}; + return if ($newlevel <= $oldlevel); + + my $wname = $win->{'name'}; + my $wtag = $win->{'server'}->{'tag'} // ''; + my ($th, $tth, $match) = get_win_threshold($wname, $wtag); + my $inhibit = $newlevel > 0 && $newlevel < $th; + debugprint(sprintf(DEBUGEVENTFORMAT, 'window', $wtag, + $wname?$wname:'(unnamed)', $newlevel, + $inhibit ? ('<',$th,'inhibit'):('≥',$th,'pass'), + $tth, $match)); + inhibit_win_hilight($win) if $inhibit; + } +} +Irssi::signal_add_first('window hilight', \&maybe_inhibit_win_hilight); + +sub maybe_inhibit_beep { + Irssi::signal_stop() if $_inhibit_beep; +} +Irssi::signal_add_first('beep', \&maybe_inhibit_beep); + +### SAVING AND LOADING ######################################################### + +sub get_mappings_fh { + my ($filename) = @_; + my $fh; + if (-e $filename) { + open($fh, '<', $filename) || croak "Cannot open mappings file: $!"; + } + else { + open($fh, '+>', $filename) || croak "Cannot create mappings file: $!"; + + my $ftw = from_data_level($fallback_window_threshold); + my $ftc = from_data_level($fallback_channel_threshold); + my $ftq = from_data_level($fallback_query_threshold); + print $fh <<"EOF"; +# ctrlact mappings file (version: $VERSION) +# +# type: window, channel, query +# server: the server tag (chatnet) +# name: full name to match, /regexp/, or * (for all) +# min.level: none, messages, hilights, all, or 1,2,3,4 +# +# type server name min.level + + +# EXAMPLES +# +### only indicate activity in the status window if messages were displayed: +# window * (status) messages +# +### never ever indicate activity for any item bound to this window: +# window * oubliette none +# +### indicate activity on all messages in debian-related channels on OFTC: +# channel oftc /^#debian/ messages +# +### display any text (incl. joins etc.) for the '#madduck' channel: +# channel * #madduck all +# +### otherwise ignore everything in channels, unless a hilight is triggered: +# channel * * hilights +# +### make somebot only get your attention if they hilight you: +# query efnet somebot hilights +# +### otherwise we want to see everything in queries: +# query * * all + +# DEFAULTS: +# window * * $ftw +# channel * * $ftc +# query * * $ftq + +# vim:noet:tw=0:ts=16 +EOF + Irssi::print("ctrlact: created new/empty mappings file: $filename"); + seek($fh, 0, 0) || croak "Cannot rewind $filename."; + } + return $fh; +} + +sub load_mappings { + my ($filename) = @_; + @window_thresholds = @channel_thresholds = @query_thresholds = (); + my $fh = get_mappings_fh($filename); + my $firstline = <$fh> || croak "Cannot read from $filename.";; + my $version; + if ($firstline =~ m/^#+\s+ctrlact mappings file \(version: *([\d.]+)\)/) { + $version = $1; + } + else { + croak "First line of $filename is not a ctrlact header."; + } + + my $nrcols = 4; + if ($version eq $VERSION) { + # current version, i.e. no special handling is required. If + # previous versions require special handling, then massage the + # data or do whatever is required in the following + # elsif-clauses: + } + elsif ($version eq "1.0") { + $nrcols = 3; + } + my $linesplitter = '^\s*'.join('\s+', ('(\S+)') x $nrcols).'\s*$'; + my $l = 1; + while (<$fh>) { + $l++; + next if m/^\s*(?:#|$)/; + my ($type, @matchers) = m/$linesplitter/; + @matchers = ['*', @matchers] if ($version eq "1.0"); + push @matchers, $l; + push @window_thresholds, [@matchers] if match($type, 'window'); + push @channel_thresholds, [@matchers] if match($type, 'channel'); + push @query_thresholds, [@matchers] if match($type, 'query'); + } + close($fh) || croak "Cannot close mappings file: $!"; +} + +sub cmd_load { + Irssi::print("ctrlact: loading mappings from $map_file"); + load_mappings($map_file); + $changed_since_last_save = 0; +} + +sub cmd_save { + error("saving not yet implemented"); + return 1; +} + +sub cmd_list { + Irssi::print("ctrlact: window mappings"); + Irssi::print(get_mappings_table(@window_thresholds), MSGLEVEL_CRAP); + Irssi::print("ctrlact: channel mappings"); + Irssi::print(get_mappings_table(@channel_thresholds), MSGLEVEL_CRAP); + Irssi::print("ctrlact: query mappings"); + Irssi::print(get_mappings_table(@query_thresholds), MSGLEVEL_CRAP); +} + +sub parse_args { + my (@args) = @_; + my @words = (); + my $typewasset = 0; + my $tag; + my $max = 0; + my $type = undef; + foreach my $arg (@args) { + if ($arg =~ m/^-(windows?|channels?|quer(?:ys?|ies))/) { + if ($typewasset) { + error("can't specify -$1 after -$type"); + return 1; + } + $type = 'window' if $1 =~ m/^w/; + $type = 'channel' if $1 =~ m/^c/; + $type = 'query' if $1 =~ m/^q/; + $typewasset = 1 + } + elsif ($arg =~ m/-(\S+)/) { + $tag = $1; + } + else { + push @words, $arg; + $max = length $arg if length $arg > $max; + } + } + return ($type, $tag, $max, @words); +} + +sub cmd_query { + my ($data, $server, $item) = @_; + my @args = shellwords($data); + my ($type, $tag, $max, @words) = parse_args(@args); + $type = $type // 'channel'; + $tag = $tag // '*'; + foreach my $word (@words) { + my ($t, $tt, $match) = get_specific_threshold($type, $word, $tag); + printf CLIENTCRAP "ctrlact $type map: %s %*s → %d (%s, match:%s)", $tag, $max, $word, $t, $tt, $match; + } +} + +sub cmd_show { + my ($data, $server, $item) = @_; + my @args = shellwords($data); + my ($type, $max, @words) = parse_args(@args); + $type = $type // 'all'; + + if ($type eq 'channel' or $type eq 'all') { + print_levels_for_all('channel', Irssi::channels()); + } + if ($type eq 'query' or $type eq 'all') { + print_levels_for_all('query', Irssi::queries()); + } + if ($type eq 'window' or $type eq 'all') { + print_levels_for_all('window', Irssi::windows()); + } +} + +sub autosave { + cmd_save() if ($changed_since_last_save); +} + +sub UNLOAD { + autosave(); +} + +Irssi::signal_add('setup saved', \&autosave); +Irssi::signal_add('setup reread', \&cmd_load); + +Irssi::command_bind('ctrlact help',\&cmd_help); +Irssi::command_bind('ctrlact reload',\&cmd_load); +Irssi::command_bind('ctrlact load',\&cmd_load); +Irssi::command_bind('ctrlact save',\&cmd_save); +Irssi::command_bind('ctrlact list',\&cmd_list); +Irssi::command_bind('ctrlact query',\&cmd_query); +Irssi::command_bind('ctrlact show',\&cmd_show); + +Irssi::command_bind('ctrlact' => sub { + my ( $data, $server, $item ) = @_; + $data =~ s/\s+$//g; + if ($data) { + Irssi::command_runsub('ctrlact', $data, $server, $item); + } + else { + cmd_help(); + } + } +); +Irssi::command_bind('help', sub { + $_[0] =~ s/\s+$//g; + return unless $_[0] eq 'ctrlact'; + cmd_help(); + Irssi::signal_stop(); + } +); + +cmd_load(); diff --git a/scripts/dccself.pl b/scripts/dccself.pl index e944133..1a6ccea 100644 --- a/scripts/dccself.pl +++ b/scripts/dccself.pl @@ -1,7 +1,8 @@ use strict; -use vars qw/%IRSSI/; +use vars qw/%IRSSI $VERSION/; use Irssi qw(command_bind active_server); +$VERSION= "0.1"; %IRSSI = ( authors => "David Leadbeater", contact => "dgl\@dgl.cx", diff --git a/scripts/desktop-notify.pl b/scripts/desktop-notify.pl index da25b8c..c41a9f4 100644 --- a/scripts/desktop-notify.pl +++ b/scripts/desktop-notify.pl @@ -24,8 +24,9 @@ use strict; use Irssi; use HTML::Entities; use Glib::Object::Introspection; # Ignore 'late INIT' warning message if autoloading +use Encode; -our $VERSION = '1.0.0'; +our $VERSION = '1.0.1'; our %IRSSI = ( authors => 'Felipe F. Tonello', contact => 'eu@felipetonello.com', @@ -38,6 +39,7 @@ our %IRSSI = ( # List of standard icons can be found here: # http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html#names my $notify_icon; +my $term_charset; my $help = ' /set notify_icon <icon-name> @@ -60,6 +62,7 @@ sub UNLOAD { sub setup_changed { $notify_icon = Irssi::settings_get_str('notify_icon'); + $term_charset = Irssi::settings_get_str('term_charset'); } sub priv_msg { @@ -71,9 +74,9 @@ sub priv_msg { return; } - my $msg = HTML::Entities::encode_entities(Irssi::strip_codes($msg)); + my $msg = HTML::Entities::encode_entities(Irssi::strip_codes($msg), "\<>&'"); my $network = $server->{tag}; - my $noti = Notify::Notification->new($nick . '@' . $network, $msg, $notify_icon); + my $noti = Notify::Notification->new($nick . '@' . $network, decode($term_charset, $msg), $notify_icon); $noti->show(); } @@ -94,8 +97,8 @@ sub hilight { } my $network = $server->{tag}; - my $msg = HTML::Entities::encode_entities($stripped); - my $noti = Notify::Notification->new($dest->{target} . '@' . $network, $msg, $notify_icon); + my $msg = HTML::Entities::encode_entities($stripped, "\'<>&"); + my $noti = Notify::Notification->new($dest->{target} . '@' . $network, decode($term_charset, $msg), $notify_icon); $noti->show(); } diff --git a/scripts/go.pl b/scripts/go.pl index b656a0f..0b0a2a2 100644 --- a/scripts/go.pl +++ b/scripts/go.pl @@ -7,8 +7,29 @@ use Irssi::Irc; # /script load go.pl # If you are in #irssi you can type /go #irssi or /go irssi or even /go ir ... # also try /go ir<tab> and /go <tab> (that's two spaces) +# +# The following settings exist: +# +# /SET go_match_case_sensitive [ON|OFF] +# Match window/item names sensitively (the default). Turning this off +# means e.g. "/go foo" would jump to a window named "Foobar", too. +# +# /SET go_match_anchored [ON|OFF] +# Match window/names only at the start of the word (the default). Turning +# this off will mean that strings can match anywhere in the window/names. +# The leading '#' of channel names is optional either way. +# +# /SET go_complete_case_sensitive [ON|OFF] +# When using tab-completion, match case-insensitively (the default). +# Turning this on means that "/go foo<tab>" will *not* suggest "Foobar". +# +# /SET go_complete_anchored [ON|OFF] +# Match window/names only at the start of the word. The default is 'off', +# which causes completion to match anywhere in the window/names during +# completion. The leading '#' of channel names is optional either way. +# -$VERSION = '1.01'; +$VERSION = '1.1'; %IRSSI = ( authors => 'nohar', @@ -16,9 +37,17 @@ $VERSION = '1.01'; name => 'go to window', description => 'Implements /go command that activates a window given a name/partial name. It features a nice completion.', license => 'GPLv2 or later', - changed => '2014-10-19' + changed => '2017-02-02' ); +sub _make_regexp { + my ($name, $ci, $aw) = @_; + my $re = "\Q${name}\E"; + $re = "(?i:$re)" unless $ci; + $re = "^#?$re" if $aw; + return $re; +} + sub signal_complete_go { my ($complist, $window, $word, $linestart, $want_space) = @_; my $channel = $window->get_active_name(); @@ -26,11 +55,14 @@ sub signal_complete_go { return unless ($linestart =~ /^\Q${k}\Ego\b/i); + my $re = _make_regexp($word, + Irssi::settings_get_bool('go_complete_case_sensitive'), + Irssi::settings_get_bool('go_complete_anchored')); @$complist = (); foreach my $w (Irssi::windows) { my $name = $w->get_active_name(); if ($word ne "") { - if ($name =~ /\Q${word}\E/i) { + if ($name =~ $re) { push(@$complist, $name) } } else { @@ -45,9 +77,13 @@ sub cmd_go my($chan,$server,$witem) = @_; $chan =~ s/ *//g; + my $re = _make_regexp($chan, + Irssi::settings_get_bool('go_match_case_sensitive'), + Irssi::settings_get_bool('go_match_anchored')); + foreach my $w (Irssi::windows) { my $name = $w->get_active_name(); - if ($name =~ /^#?\Q${chan}\E/) { + if ($name =~ $re) { $w->set_active(); return; } @@ -56,4 +92,14 @@ sub cmd_go Irssi::command_bind("go", "cmd_go"); Irssi::signal_add_first('complete word', 'signal_complete_go'); +Irssi::settings_add_bool('go', 'go_match_case_sensitive', 1); +Irssi::settings_add_bool('go', 'go_complete_case_sensitive', 0); +Irssi::settings_add_bool('go', 'go_match_anchored', 1); +Irssi::settings_add_bool('go', 'go_complete_anchored', 0); +# Changelog +# +# 2017-02-02 1.1 martin f. krafft <madduck@madduck.net> +# - made case-sensitivity of match configurable +# - made anchoring of search strings configurable +# diff --git a/scripts/hilightwin.pl b/scripts/hilightwin.pl index 7d70317..2e407fa 100644 --- a/scripts/hilightwin.pl +++ b/scripts/hilightwin.pl @@ -11,7 +11,7 @@ use Irssi; use POSIX; use vars qw($VERSION %IRSSI); -$VERSION = "0.04"; +$VERSION = "0.05"; %IRSSI = ( authors => "Timo \'cras\' Sirainen, Mark \'znx\' Sangster", contact => "tss\@iki.fi, znxster\@gmail.com", @@ -22,6 +22,26 @@ $VERSION = "0.04"; changed => "Sun May 25 18:59:57 BST 2008" ); +sub is_ignored { + my ($dest) = @_; + + my @ignore = split(' ', Irssi::settings_get_str('hilightwin_ignore_targets')); + return 0 if (!@ignore); + + my %targets = map { $_ => 1 } @ignore; + + return 1 if exists($targets{"*"}); + return 1 if exists($targets{$dest->{target}}); + + if ($dest->{server}) { + my $tag = $dest->{server}->{tag}; + return 1 if exists($targets{$tag . "/*"}); + return 1 if exists($targets{$tag . "/" . $dest->{target}}); + } + + return 0; +} + sub sig_printtext { my ($dest, $text, $stripped) = @_; @@ -33,7 +53,8 @@ sub sig_printtext { if( ($dest->{level} & ($opt)) && - ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0 + ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0 && + (!is_ignored($dest)) ) { my $window = Irssi::window_find_name('hilight'); @@ -49,6 +70,7 @@ my $window = Irssi::window_find_name('hilight'); Irssi::print("Create a window named 'hilight'") if (!$window); Irssi::settings_add_bool('hilightwin','hilightwin_showprivmsg',1); +Irssi::settings_add_str('hilightwin', 'hilightwin_ignore_targets', ''); Irssi::signal_add('print text', 'sig_printtext'); diff --git a/scripts/hilite_url.pl b/scripts/hilite_url.pl new file mode 100644 index 0000000..11c4b0d --- /dev/null +++ b/scripts/hilite_url.pl @@ -0,0 +1,28 @@ +# Simple script to highlight links in public messages + +use strict; +use vars qw($VERSION %IRSSI); + +# Dev. info ^_^ +$VERSION = "0.1"; +%IRSSI = ( + authors => "Stefan Heinemann", + contact => "stefan.heinemann\@codedump.ch", + name => "hilite url", + description => "Simple script that highlights URL", + license => "GPL", + url => "http://senseless.codedump.ch", +); + +sub hilite_url { + my ($server, $data, $nick, $mask, $target) = @_; + + # Add Colours + $data =~ s/(https?:\/\/[^\s]+)/\e[4;34m\1\e[00m/g; + + # Let it flow + Irssi::signal_continue($server, $data, $nick, $mask, $target); +} + +# Hook me up +Irssi::signal_add('message public', 'hilite_url'); diff --git a/scripts/ident.pl b/scripts/ident.pl new file mode 100644 index 0000000..a4854f2 --- /dev/null +++ b/scripts/ident.pl @@ -0,0 +1,68 @@ +#!/usr/bin/perl -w + +use strict; +use Irssi; +use POSIX; + +use vars qw($VERSION %IRSSI); + +$VERSION = "1.0"; +%IRSSI = ( + authors => 'Isaac Good', + contact => "irssi\@isaacgood.com; irc.freenode.net/yitz", + name => 'ident', + description => 'Ident to NickServs', + name => "ident", + description => "Automatically IDENTIFY when prompted", + license => 'MIT', +); + + +my %pw; + + +sub LoadPasswords { + # Load the passwords from file. + delete @pw{keys %pw}; + my $filename = Irssi::get_irssi_dir() . '/passwords'; + my $FH; + unless(open $FH, "<", $filename) + { + print "Can not open $filename"; + return 0; + } + while (my $line = <$FH>) + { + chomp $line; + next unless ($line); + my ($tag, $password) = split(/ */, $line, 2); + next unless ($tag and $password); + $pw{$tag} = $password; + } + return 1; +} + + +sub notice { + my ($server, $data, $nick, $host) = @_; + my ($channel, $msg) = split(/ :/, $data, 2); + my $l = 0; + + # Test the notice. Must be from nickserv and be asking you to identify. + return undef unless (lc($nick) eq 'nickserv'); + return undef unless (lc($msg) =~ /msg nickserv identify/); + # Check it's a direct message and we have a password for this network. + return undef unless (lc($channel) eq lc($server->{'nick'})); + return undef unless ($pw{$server->{'chatnet'}}); + + my $pw = $pw{$server->{'chatnet'}}; + # Use the /quote nickserv approach to reduce chance of leaking the password to a bad actor, ie someone pretending to be nickserv. + $server->command("^quote nickserv identify $pw"); + + return undef; +} + + +if (LoadPasswords()) { + Irssi::signal_add('event notice', \¬ice); +} diff --git a/scripts/invitejoin.pl b/scripts/invitejoin.pl index c69ed01..d3b5871 100644 --- a/scripts/invitejoin.pl +++ b/scripts/invitejoin.pl @@ -24,7 +24,7 @@ use strict; use Irssi; use vars qw($VERSION %IRSSI); -$VERSION = "0.01"; +$VERSION = '0.02'; %IRSSI = ( authors => 'Geert Hauwaerts', @@ -32,37 +32,254 @@ $VERSION = "0.01"; name => 'invitejoin.pl', description => 'This script will join a channel if somebody invites you to it.', license => 'Public Domain', - url => 'http://irssi.hauwaerts.be/invitejoin.pl', - changed => 'Sun Apr 11 12:38:18 2004', + url => 'https://github.com/irssi/scripts.irssi.org/blob/master/scripts/invitejoin.pl', + changed => 'Di 17. Jan 19:32:45 CET 2017', ); -## Comments and remarks. -# -# This script uses settings. -# Use /SET to change the value or /TOGGLE to switch it on or off. -# -# Setting: invitejoin -# Description: If this setting is turned on, you will join the channel -# when invite to. -# -## +my $help = <<EOF; + +/SET invitejoin 0|1 +/TOGGLE invitejoin + Description: If this setting is turned on, you will join the channel + when invited to. + +Default is to follow every invite, you can specify a list of allowed nicks. + +/INVITEJOIN [addnick <ircnet> <nick>] + [delnick <ircnet> <nick>] + [listnick] + [help] + +addnick: Add a new nickname on the given net as allowed autoinvite source. +delnick: Delete a nickname from the allowed list. +listnick: Display the contents of the allowed nickname list. +help: Display this useful little helptext. + +Examples: (all on one line) +/INVITEJOIN addnick Freenode ChanServ + +Note: This script doesn't allow wildcards +EOF +my @allowed_nicks = (); +my $allowed_nicks_file = "invitejoin.nicks"; + +my $irssidir = Irssi::get_irssi_dir(); Irssi::theme_register([ + 'invitejoin_usage', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Use "%_/INVITEJOIN help%_" for further instructions.', + 'invitejoin_help', '$0', 'invitejoin_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.', - 'invitejoin_invited', '%R>>%n %_Invitejoin:%_ Joined $1 (Invited by $0).' + 'invitejoin_invited', '%R>>%n %_Invitejoin:%_ Joined $1 (Invited by $0).', + 'invitejoin_usage_add_nick', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Usage "%_/INVITEJOIN addnick ircnet ChanServ%_".', + 'invitejoin_no_net', '%R>>%n %_Invitejoin:%_ Unknown Irssi ircnet %_$0%_.', + 'saved_nick', '%R>>%n %_Invitejoin:%_ Added allowed nick "%_$1%_" on %_$0%_.', + 'nick_already_present', '%R>>%n %_Invitejoin:%_ Nick already present.', + 'invitejoin_delusage', '%R>>%n %_Invitejoin:%_ Insufficient parameters: Usage "%_/INVITEJOIN delnick ircnet nick%_".', + 'invitejoin_delled', '%R>>%n %_Invitejoin:%_ Deleted %_$1%_ on %_$0%_ from allowed list.', + 'invitejoin_nfound', '%R>>%n %_Invitejoin:%_ The nick %_$1%_ on %_$0%_ could not be found.', + 'allowed_nicks_info', '%_Ircnet Nick%_', + 'allowed_nicks_empty', '%R>>%n %_Invitejoin:%_ Your allowed nick list is empty. All invites will be followed.', + 'allowed_nicks_print', '$[18]0 $1', + 'invite_denied', '%R>>%n %_Invitejoin:%_ Invite from nick %_$1%_ on %_$0%_ to %_$2%_ not followed because it is not in the allowed list.', ]); +sub load_allowed_nicks { + my ($file) = @_; + + @allowed_nicks = load_file($file, sub { + my $new_allowed = new_allowed_nick(@_); + + return undef if ($new_allowed->{net} eq '' || $new_allowed->{nick} eq ''); + return $new_allowed; + }); +} + +sub save_allowed_nicks { + my ($file) = @_; + save_file($file, \@allowed_nicks, \&allowed_nick_to_list); +} + +sub allowed_nick_to_list { + my $allowed_nick = shift; + + return ( + $allowed_nick->{net}, + $allowed_nick->{nick} + ); +} + +sub new_allowed_nick { + return { + net => shift, + nick => shift + }; +} + +# file: filename to be read +# parse_line_fn: receives array of entries of a single line as input, should +# return parsed data object or undef in the data is incomplete +# returns: parsed data array +sub load_file { + my ($file, $parse_line_fn) = @_; + my @parsed_data = (); + + if (-e $file) { + open(my $fh, "<", $file); + local $/ = "\n"; + + while (<$fh>) { + chomp; + my $data = $parse_line_fn->(split("\t")); + push(@parsed_data, $data) if $data; + } + + close($fh); + } + + return @parsed_data; +} + +# file: filename to be written, is created accessable only by the user +# data_ref: array ref of data entries +# serialize_fn: receives a data reference and should return an array or tuples +# for that data that will be serialized into one line +sub save_file { + my ($file, $data_ref, $serialize_fn) = @_; + + create_private_file($file) unless -e $file; + + open(my $fh, ">", $file) or die "Can't create $file. Reason: $!"; + + for my $data (@$data_ref) { + print($fh join("\t", $serialize_fn->($data)), "\n"); + } + + close($fh); +} + +sub create_private_file { + my ($file) = @_; + my $umask = umask 0077; # save old umask + open(my $fh, ">", $file) or die "Can't create $file. Reason: $!"; + close($fh); + umask $umask; +} + +sub add_allowed_nick { + my ($network, $nick) = split(" ", $_[0], 2); + my ($correct_net); + + if ($network eq '' || $nick eq '') { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_usage_add_nick'); + return; + } + + if ($network) { + my ($ircnet) = Irssi::chatnet_find($network); + if (!$ircnet) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_no_net', $network); + return; + } else { + $correct_net = 1; + } + } + + if ($correct_net && $nick) { + if (is_nick_in_list($network, $nick)) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nick_already_present'); + return; + } + + push(@allowed_nicks, new_allowed_nick($network, $nick)); + save_allowed_nicks("$irssidir/$allowed_nicks_file"); + + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'saved_nick', $network, $nick); + } +} + +sub del_allowed_nick { + my ($ircnet, $nick) = split(" ", $_[0], 2); + + if ($ircnet eq '' || $nick eq '') { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_delusage'); + return; + } + + my $size_before = scalar(@allowed_nicks); + @allowed_nicks = grep { ! ($_->{net} eq $ircnet && $_->{nick} eq $nick) } @allowed_nicks; + my $size_after = scalar(@allowed_nicks); + + if ($size_after != $size_before) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_delled', $ircnet, $nick); + save_allowed_nicks("$irssidir/$allowed_nicks_file"); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_nfound', $ircnet, $nick); + } + + if ($size_after == 0) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_empty'); + } +} + +sub list_allowed_nicks { + if (@allowed_nicks == 0) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_empty'); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_info'); + + for my $allowed (@allowed_nicks) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'allowed_nicks_print', $allowed->{net}, $allowed->{nick}); + } + } +} + +sub invitejoin_runsub { + my ($data, $server, $item) = @_; + $data =~ s/\s+$//g; + + if ($data) { + Irssi::command_runsub('invitejoin', $data, $server, $item); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_usage'); + } +} + +sub is_nick_in_list { + my ($net, $nick) = @_; + + return (grep { + $_->{net} eq $net && + $_->{nick} eq $nick + } @allowed_nicks) > 0; +} + +sub is_allowed_nick { + my ($net, $nick) = @_; + + # If no allowed nicks are specified (initial configuration) accept + # all invite requests. + # # (This mimics previous behavior of this script + # before there was an allowed list) + return 1 if @allowed_nicks == 0; + + return is_nick_in_list($net, $nick); +} + sub invitejoin { - my ($server, $channel, $nick, $address) = @_; my $invitejoin = Irssi::settings_get_bool('invitejoin'); if ($invitejoin) { - $server->command("join $channel"); - - Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_invited', $nick, $channel); - Irssi::signal_stop(); + if (is_allowed_nick($server->{tag}, $nick)) { + $server->command("join $channel"); + + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_invited', $nick, $channel); + Irssi::signal_stop(); + } + else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invite_denied', $server->{tag}, $nick, $channel); + } } } @@ -70,4 +287,12 @@ Irssi::signal_add('message invite', 'invitejoin'); Irssi::settings_add_bool('invitejoin', 'invitejoin' => 1); +load_allowed_nicks("$irssidir/$allowed_nicks_file"); + +Irssi::command_bind('invitejoin', 'invitejoin_runsub'); +Irssi::command_bind('invitejoin addnick', 'add_allowed_nick'); +Irssi::command_bind('invitejoin delnick', 'del_allowed_nick'); +Irssi::command_bind('invitejoin listnick', 'list_allowed_nicks'); +Irssi::command_bind('invitejoin help' => sub { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_help', $help) }); + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'invitejoin_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors}); diff --git a/scripts/iquiz.pl b/scripts/iquiz.pl new file mode 100644 index 0000000..336f569 --- /dev/null +++ b/scripts/iquiz.pl @@ -0,0 +1,1261 @@ +################################################################## +## irssi Quiz (iQuiz) script (2010-2016) by wilk/xorandor ## +################################################################## +## Script inspired by classic mIRC scripts: "Dizzy" by Dizzy, ## +## "Mieszacz" & "Familiada" by snajperx (both with my later ## +## upgrades). ## +## Other credits: ## +## Bjoern 'fuchs' Krombholz for splitlong.pl calculations ## +################################################################## + +# Tested more or less with irssi 0.8.15 & 0.8.18 and Perl 5.8.8, 5.10.1, 5.14.2, 5.16.3 & 5.18.2 + +# Script works with: +# +# - standard Dizzy/Pomieszany files (also without "pyt"/"odp" prefixes): +# +# pyt Evaluate: 2+2=? +# odp four +# pyt Star closest to Earth? +# odp Sun +# ... +# +# - standard Mieszacz files (also without line numbers): +# +# 1 alpha +# 2 beta +# 3 gamma +# 4 delta +# ... +# +# - standard Familiada files (can have any number of answers per question, used also for Multi): +# +# Planets of our Solar System: +# Mercury*Venus*Earth*Mars*Jupiter*Saturn*Uranus*Neptune +# First six alkanes: +# methane*ethane*propane*butane*pentane*hexane +# ... + +# >>> To view all available commands and settings type: /quiz + +# only core modules +use strict; +use warnings; +use Irssi qw(theme_register current_theme command_bind settings_add_int settings_add_bool settings_add_str settings_get_int settings_get_bool settings_get_str settings_set_int settings_set_bool settings_set_str printformat timeout_add_once timeout_remove signal_add_last signal_remove signal_stop signal_emit active_win); +use Time::HiRes qw(time); +use constant { QT_STD => 1, QT_MIX => 2, QT_FAM => 3, QT_MUL => 4, QT_SCR => 5 }; # QT_MIL => 6, QT_FOR => 7 + +our $VERSION = '160919'; +our %IRSSI = ( + authors => 'wilk', + name => 'iQuiz', + description => 'irssi quiz script', # one script to bind them all + license => 'GNU GPL v3 or any later version', + changed => (($VERSION =~ /^(\d\d)(\d\d)(\d\d)/) ? "20$1-$2-$3" : $VERSION), + url => 'http://iquiz.quizpl.net', + contact => 'http://mail.quizpl.net', + changes => 'see http://www.quizpl.net/viewtopic.php?f=3&t=404', + usage => 'see http://www.quizpl.net/viewtopic.php?f=3&t=587' +); + +##### Hardcoded settings ##### +my $_display_delay = 100; # msec; workaround for display issue (response before request) +my $_start_delay = 5000; # msec; delay between /qon and showing first question (or 0) +my $_standby_delay = 1000; # msec; delay between /qon and showing first question (or 0) while on standby +my $_max_teams = 5; # int; max allowed teams (5 is reasonable) +my $_shuffle_watchdog = 10; # int; max shuffling repetitions to prevent mixed == original, but avoid infinite loop +my $_shuffle_threshold = 3; # int; below this length reshuffling is off (to prevent mixed == original) +my $_randomized_antigoogler = 0; # bool; use better, randomized antigoogler? (will increase question length) +my $_smarter_antigoogler = 1; # bool; use smarter antigoogler? (leaves some empty spaces for better line breaking) +my $_smarter_antigoogler_chunk = 2; # int; leaves empty space every after this many substitutions (for use with $_smarter_antigoogler) +my $_protect_urls = 1; # bool; turn off antigoogler if URL is detected in question? +my $_round_warn_time = 15; # sec; seconds before round end to show warning (0 = off) +my $_round_warn_coeff = 1.5; # float; round duration must be longer than coeff * $_round_warn_time to show warning (protection) +my $_qstats_ranks = 0; # bool; 0: /qstats param corresponds to number of players, 1: /qstats param corresponds to rank +my $_qstats_records = 5; # int; number of time/speed record places in /qstats + +my $_next_delay = 10; # sec; default delay between questions +my $_next_delay_long = 20; # sec; default delay between questions (fam/mul) (longer delay to prevent flooding and give a breath) +my $_round_duration = 90; # sec; default round duration +my $_hint_alpha = '.'; # char; default substitution symbol for alphabet characters in hints (special characters are left intact) +my $_hint_digit = '.'; # char; default substitution symbol for digit characters in hints (special characters are left intact) + +my $_quiz_types = 5; # (do not change) + +##### Internal stuff ##### +use constant { T_HMS => 0, T_S => 1, T_MS => 2 }; # 0: h/m/s, 1: s only, 2: s.ms +use constant { INSTANT => 1, PREPDOTS => 1, V_INT => 1, V_BOOL => 2, V_STR => 3 }; + +my %quiz = ( + chan => undef, file => '', + type => 0, tcnt => 0, # copies just in case someone modifies settings directly while quiz is running + ison => 0, inq => 0, standby => 0, + stime => 0, qtime => 0, + qcnt => 0, qnum => 0, hnum => 0, + score => 0, answers => 0, + tnext => undef, tround => undef, thint => undef, tremind => undef, twarn => undef, + hprot => 0, rprot => 0, + data => [], # data[]{question realquestion answer answers{}} + teams => [], # teams[]{score answers} + players => {}, # players{}{nick timestamp score answers team besttime alltime bestspeed allspeed} + lookup => {}, dcnt => 0, dmax => 0, lmax => 0, dots => [], hwords => [] +); + +my %settings_int = ( + 'quiz_type' => 1, + 'quiz_teams' => 2, + 'quiz_delay' => $_next_delay, + 'quiz_delay_long' => $_next_delay_long, + 'quiz_round_duration' => $_round_duration, + 'quiz_max_hints' => 0, + 'quiz_words_style' => 0, + 'quiz_anticheat_delay' => 3, + 'quiz_first_anticheat_delay' => 7, + 'quiz_points_per_answer' => 1, + 'quiz_min_points' => 1, + 'quiz_max_points' => 50, + 'quiz_scoring_mode' => 4, + 'quiz_ranking_type' => 3, +); + +my %settings_bool = ( + 'quiz_antigoogler' => 1, + 'quiz_split_long_lines' => 1, + 'quiz_show_first_hint' => 0, + 'quiz_first_hint_dots' => 0, + 'quiz_random_hints' => 1, + 'quiz_nonrandom_first_hint' => 1, + 'quiz_words_mode' => 1, + 'quiz_smart_mix' => 1, + 'quiz_mix_on_remind' => 1, + 'quiz_strict_match' => 1, + 'quiz_join_anytime' => 1, + 'quiz_team_play' => 1, + 'quiz_transfer_points' => 0, + 'quiz_limiter' => 0, + 'quiz_keep_scores' => 0, + 'quiz_cmd_hint' => 1, + 'quiz_cmd_remind' => 1, +); + +my %settings_str = ( + 'quiz_hint_alpha' => $_hint_alpha, + 'quiz_hint_digit' => $_hint_digit, + 'quiz_smart_mix_chars' => '\d()",.;:?!', +); + +##### Theme (only channel messages are localized by default, feel free to customize here or via /format, except authorship) ##### +# quiz_inf_*, quiz_wrn_* & quiz_err_* messages are irssi only - use irssi formatting and irssi color codes +# quiz_msg_* messages are sent on channel - use sprintf formatting and mIRC color codes: +# \002 - bold \003$fg(,$bg)? - color \017 - plain \026 - reverse \037 - underline +# quiz_inc_* - not sent directly, used as inclusions +# quiz_flx_* - not sent directly, words' inflections +# Important: To prevent visual glitches use two digit color codes! i.e. \00304 instead of \0034 +theme_register([ + 'quiz_inf_start', '%_iQuiz:%_ Aby uzyskac pomoc wpisz: /quiz', + 'quiz_inf_delay', '%_iQuiz:%_ %gZmieniono opoznienie miedzy pytaniami na: %_$0%_ sek.%n', + 'quiz_inf_duration', '%_iQuiz:%_ %gZmieniono czas trwania rundy na: %_$0%_ sek.%n', + 'quiz_inf_type', '%_iQuiz:%_ %gZmieniono tryb gry na: %_$0%_%n', + 'quiz_inf_teams', '%_iQuiz:%_ %gZmieniono liczbe druzyn na: %_$0%_%n', + 'quiz_inf_reset', '%_iQuiz:%_ %gWszystkie ustawienia zostaly przywrocone do poczatkowych wartosci%n', + 'quiz_inf_reload', '%_iQuiz:%_ %gPlik z pytaniami zostal ponownie wczytany%n', + + 'quiz_wrn_reload', '%_iQuiz:%_ %YZmienila sie liczba pytan (po ponownym wczytaniu)%n', + + 'quiz_err_ison', '%_iQuiz:%_ %RQuiz jest juz uruchomiony%n', + 'quiz_err_isoff', '%_iQuiz:%_ %RQuiz nie jest uruchomiony%n', + 'quiz_err_server', '%_iQuiz:%_ %RBrak polaczenia z serwerem%n', + 'quiz_err_channel', '%_iQuiz:%_ %RBledna nazwa kanalu%n', + 'quiz_err_nochannel', '%_iQuiz:%_ %RKanal "$0" nie jest otwarty%n', + 'quiz_err_filename', '%_iQuiz:%_ %RBledna nazwa pliku%n', + 'quiz_err_nofile', '%_iQuiz:%_ %RPlik "$0" nie zostal odnaleziony%n', + 'quiz_err_file', '%_iQuiz:%_ %RPlik "$0" wydaje sie byc uszkodzony%n', + 'quiz_err_argument', '%_iQuiz:%_ %RBledny parametr polecenia%n', + 'quiz_err_noquestion', '%_iQuiz:%_ %RPoczekaj az pytanie zostanie zadane%n', + 'quiz_err_type', '%_iQuiz:%_ %RBledny tryb gry%n', + 'quiz_err_delay', '%_iQuiz:%_ %RBledna wartosc opoznienia miedzy pytaniami%n', + 'quiz_err_duration', '%_iQuiz:%_ %RBledna wartosc czasu trwania rundy%n', + 'quiz_err_teams', '%_iQuiz:%_ %RBledna liczba druzyn%n', + 'quiz_err_ranking', '%_iQuiz:%_ %RBledna liczba graczy%n', + 'quiz_err_na', '%_iQuiz:%_ %RTa funkcja jest niedostepna przy obecnych ustawieniach%n', + + 'quiz_msg', '%s', # custom text + 'quiz_msg_start1', "\00303>>> \00310iQuiz by wilk wystartowal \00303<<<", + 'quiz_msg_start2', "\00303Polecenia: !podp, !przyp, !ile, !ile nick", + 'quiz_msg_start2_f', "\00303Polecenia: !przyp, !ile, !ile nick, !join 1-%u", # 1: max teams + 'quiz_msg_start2_m', "\00303Polecenia: !przyp, !ile, !ile nick", + 'quiz_msg_stop1', "\00303>>> \00310iQuiz by wilk zakonczony \00303<<<", + 'quiz_msg_stop2', "\00303Liczba rund: \00304%u \00303Czas gry: \00304%s", # 1: round, 2: time_str (hms) + 'quiz_msg_question', "\00303\037Pytanie %u/%u:\037 %s", # see below + 'quiz_msg_question_x', "\00303\037Haslo %u/%u:\037 %s", # see below + 'quiz_msg_question_fm', "\00303\037Pytanie %u/%u:\037 %s \00303(\00313%u\00303 %s, czas: %u sek.)", # 1: round, 2: rounds, 3: question (quiz_inc_question), 4: answers, 5: quiz_flx_answers, 6: round time (s) + 'quiz_inc_question', "\00300,01 %s \017", # 1: question (antygoogler takes first color code to harden question - must use background color if using antigoogler; if any color is used finish with "\017" to reset it) + 'quiz_msg_hint', "\00303Podpowiedz: \00304%s", # 1: hint + 'quiz_inc_hint_alpha', "\00310%s\00304", # 1: symbol (color codes are used to distinguish between hidden letter and real dot, but you may omit them) + 'quiz_inc_hint_digit', "\00310%s\00304", # 1: symbol (same as above) + 'quiz_msg_remind', "\00303Przypomnienie: %s", # 1: question (quiz_inc_question) + 'quiz_msg_delay', "\00303Opoznienie miedzy pytaniami: \00304%u\00303 sek.", # 1: time (s) + 'quiz_msg_duration', "\00303Czas trwania rundy: \00304%u\00303 sek.", # 1: time (s) + 'quiz_msg_score', "\00304%s\00303\002\002, zdobyles(as) jak dotad \00304%d\00303 %s.", # 1: nick, 2: score, 3: quiz_flx_points + 'quiz_msg_noscore', "\00304%s\00303\002\002, nie zdobyles(as) jeszcze zadnego punktu!", # 1: nick + 'quiz_msg_score_other', "\00304%s\00303 zdobyl(a) jak dotad \00304%d\00303 %s.", # see quiz_msg_score + 'quiz_msg_noscore_other', "\00304%s\00303 nie zdobyl(a) jeszcze zadnego punktu!", # 1: nick + 'quiz_msg_noscores', "\00303Tablica wynikow jest jeszcze pusta.", + 'quiz_msg_scores', "\00303Wyniki quizu po %s i %u %s:", # 1: time_str (hms), 2: question, 3: quiz_flx_questions, 4: questions (total), 5: quiz_flx_questions (total) + 'quiz_msg_scores_place', "\00303%u. miejsce: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (sr. czas zgadywania: %10\$.3f sek.)", # 1: place, 2: nick, 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers%, 9: best time, 10: avg time, 11: best speed, 12: avg speed, 13: spacer + 'quiz_msg_scores_place_full', "\00303%u. miejsce: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (%u %s, sr. czas zgadywania: %10\$.3f sek.)", # see quiz_msg_scores_place + 'quiz_msg_team_score', "\00303Druzyna %u (%s): \00304%d\00303 %s", # 1: team, 2: players (comma separated), 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers% + 'quiz_msg_team_score_full', "\00303Druzyna %u (%s): \00304%d\00303 %s (%6\$u %7\$s)", # see quiz_msg_team_score + 'quiz_msg_team_join', "\00303Dolaczyles(as) do Druzyny %u (%s).", # 1: team, 2: players (comma separated) + 'quiz_inc_team_nick', "\00307%s\00303", # 1: nick + 'quiz_msg_scores_times', "\00303Najszybsi (czas): %s", # 1: players (comma separated) + 'quiz_msg_scores_speeds', "\00303Najszybsi (zn/s): %s", # 1: players (comma separated) + 'quiz_inc_scores_record', "\00303%u. \00304%s\00303 (%.3f)", # 1: place, 2: nick, 3: time/speed record + 'quiz_msg_congrats', "\00303Brawo, \00304%s\00303! Dostajesz %s za odpowiedz \00304%s\00303 podana po czasie %.3f sek. (%.3f zn/s) - suma punktow: \00304%d\00303.", # 1: nick, 2: quiz_inc_got_point*, 3: answer, 4: time (ms), 5: speed (chars/s), 6: total score + 'quiz_inc_got_points', "\00304%d\00303 %s", # 1: points, 2: quiz_flx_points + 'quiz_inc_got_point', "\00303%s", # 1: quiz_flx_point + 'quiz_inc_hours', '%u godz.', # 1: hours + 'quiz_inc_minutes', '%u min.', # 1: minutes + 'quiz_inc_seconds', '%u sek.', # 1: seconds + 'quiz_inc_seconds_ms', '%.3f sek.', # 1: seconds.milliseconds + 'quiz_msg_warn_timeout', "\00307Uwaga, zostalo jeszcze tylko \00304%u\00307 sek. na odpowiadanie!", # 1: time (s) + 'quiz_msg_all_answers', "\00303Wszystkie odpowiedzi zostaly odgadniete!", + 'quiz_msg_timeout', "\00303Czas na odpowiadanie uplynal!", + 'quiz_msg_next', "\00303Nastepne pytanie za %u sek...", # 1: time (s) + 'quiz_msg_next_x', "\00303Nastepne haslo za %u sek...", # 1: time (s) + 'quiz_msg_last', "\00307Koniec pytan!", + 'quiz_msg_skipped', "\00303Pytanie zostalo pominiete.", + # 1 point / 1 punkt + # x points / x punktow + # 2-4, x2-x4 points (x != 1) / 2-4, x2-x4 punkty (x != 1) + 'quiz_flx_points', 'punkt/punktow/punkty', + # 1 answer / 1 odpowiedz + # x answers / x odpowiedzi + # 2-4, x2-x4 answers (x != 1) / 2-4, x2-x4 odpowiedzi (x != 1) + 'quiz_flx_answers', 'odpowiedz/odpowiedzi/odpowiedzi', + # after 1 question / po 1 pytaniu + # after x questions / po x pytaniach + # after 2-4, x2-x4 questions (x != 1) / po 2-4, x2-x4 pytaniach (x != 1) + 'quiz_flx_aquestions', 'pytaniu/pytaniach/pytaniach', + # from 1 question / z 1 pytania + # from x questions / z x pytan + # from 2-4, x2-x4 questions (x != 1) / z 2-4, x2-x4 pytan (x != 1) + 'quiz_flx_fquestions', 'pytania/pytan/pytan', +]); + +##### Support routines ##### +sub load_quiz { + my ($fname, $lines) = (shift, 0); + $quiz{data} = []; + $quiz{qcnt} = 0; + return 0 unless (open(my $fh, '<', $fname)); + while (<$fh>) { + s/[\n\r]//g; # chomp is platform dependent ($/) + tr/\t/ /; # tabs to spaces + s/ {2,}/ /g; # fix double spaces + s/^ +| +$//g; # trim leading/trailing spaces/tabs + next if (/^ *$/); + if (($quiz{type} == QT_STD) || ($quiz{type} == QT_SCR)) { + if ($lines % 2) { + s/^o(dp|pd) //i; # remove format (broken as well) + $quiz{data}[++$quiz{qcnt}]{answer} = $_; # ++ only on complete question + } else { + s/^p(yt|ty) //i; # remove format (broken as well) + $quiz{data}[$quiz{qcnt} + 1]{($quiz{type} == QT_STD) ? 'question' : 'realquestion'} = $_; + } + } elsif ($quiz{type} == QT_MIX) { + s/^\d+ //; # remove format + $quiz{data}[++$quiz{qcnt}]{answer} = $_; + } elsif (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + if ($lines % 2) { + s/ +\*/*/g; # fix format + s/\* +/*/g; # fix format + my $enum = 1; + # ++ only on complete question + %{$quiz{data}[++$quiz{qcnt}]{answers}} = map { $_ => $enum++ } split /\*/; + } else { + $quiz{data}[$quiz{qcnt} + 1]{question} = $_; + } + } + $lines++; + } + close($fh); + return $lines; +} + +sub get_format { + my ($format, $args) = @_; + return sprintf(current_theme()->get_format(__PACKAGE__, $format), ref($args) ? @{$args} : (defined($args) ? $args : ())); +} + +sub send_ui { + my ($format, @rest) = @_; + printformat(MSGLEVEL_CRAP, $format, @rest); +} + +sub send_ui_raw { + print CLIENTCRAP shift; +} + +sub send_irc { + my (undef, undef, $instant) = @_; + my $msg = get_format(@_); + if ($quiz{chan}{server}{connected}) { + if ($instant) { # instant or queued + $quiz{chan}{server}->send_raw_now("PRIVMSG $quiz{chan}{name} :$msg"); + } else { + $quiz{chan}{server}->send_raw("PRIVMSG $quiz{chan}{name} :$msg"); + } + timeout_add_once($_display_delay, 'evt_delayed_show_msg', $msg); # le trick (workaround for chantext showing after owntext) + } else { + send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats + send_ui('quiz_err_server'); + } +} + +sub send_irc_whisper { + my (undef, undef, $nick, $instant) = @_; + my $msg = get_format(@_); + if ($quiz{chan}{server}{connected}) { + if ($instant) { # instant or queued + $quiz{chan}{server}->send_raw_now("NOTICE $nick :$msg"); + } else { + $quiz{chan}{server}->send_raw("NOTICE $nick :$msg"); + } + timeout_add_once($_display_delay, 'evt_delayed_show_notc', [$msg, $nick]); # le trick (workaround for chantext showing after owntext) + } else { + send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats + send_ui('quiz_err_server'); + } +} + +sub shuffle_text { + my ($old, $new, $length) = (shift, '', 0); + my @old = split(//, $old); + my @mov; + my $smart = ($quiz{type} == QT_SCR) ? settings_get_bool('quiz_smart_mix') : 0; + if ($smart) { + my $chars = settings_get_str('quiz_smart_mix_chars'); #? quotemeta? + for (my $i = 0; $i < @old; $i++) { + if ($old[$i] !~ /^[$chars]$/) { # hypen, apostrophe, math symbols will float + push(@mov, $i); + $length++; + } + } + $smart = 0 if ($length == length($old)); # no punctations & digits + } else { + $length = length($old); + } + return $old if ($length < 2); # skip short (and empty) + my $watchdog = ($length < $_shuffle_threshold) ? 1 : $_shuffle_watchdog; + do { + if ($smart) { + my @new = @old; + my @tmp = @mov; + my $i = 0; + while (@tmp) { + $i++ while (!grep { $_ == $i } @mov); + my $j = splice(@tmp, int(rand(@tmp)), 1); + $new[$i++] = $old[$j]; + } + $new = join('', @new); + } else { + my @tmp = @old; + $new = ''; + $new .= splice(@tmp, int(rand(@tmp)), 1) while (@tmp); + } + } until (($old ne $new) || (--$watchdog <= 0)); + return $new; +} + +sub shuffle { + my ($text, $style) = (shift, settings_get_int('quiz_words_style')); + my $keepfmt = ($quiz{type} == QT_SCR) ? 1 : settings_get_bool('quiz_words_mode'); + if ($style == 1) { + $text = lc $text; + } elsif ($style == 2) { + $text = uc $text; + } elsif ($style == 3) { + $text = join(' ', map { ucfirst lc } split(/ /, $text)); + } + if ($keepfmt) { + return join(' ', map { shuffle_text($_) } split(/ /, $text)); + } else { + $text =~ s/ //g; + return shuffle_text($text); + } +} + +sub antigoogle { + my $text = shift; + return $text unless (settings_get_bool('quiz_antigoogler') && ($text =~ / /)); + return $text if ($_protect_urls && ($text =~ m<https?://|www\.>)); + my ($fg, $bg) = (get_format('quiz_inc_question', '') =~ /^\003(\d{1,2}),(\d{1,2})/); + return $text unless (defined($fg) && defined($bg)); + ($fg, $bg) = map { int } ($fg, $bg); + my @set = ('a'..'z', 'A'..'Z', 0..9); + my @h; my @v; + #t = \00300,01 (quiz_inc_question) + #h = \0031,01 \00301,01 \00301 + #v = \0030,01 \00300,01 \00300 + if ($bg < 10) { + push(@h, "\0030$bg"); + push(@h, "\003$bg,0$bg", "\0030$bg,0$bg") if ($_randomized_antigoogler); + } else { + push(@h, "\003$bg"); + push(@h, "\003$bg,$bg") if ($_randomized_antigoogler); + } + $bg = substr("0$bg", -2); # make sure $bg is 2-char + if ($fg < 10) { + push(@v, "\0030$fg"); + push(@v, "\003$fg,$bg", "\0030$fg,$bg") if ($_randomized_antigoogler); + } else { + push(@v, "\003$fg"); + push(@v, "\003$fg,$bg") if ($_randomized_antigoogler); + } + my @lines; + if (settings_get_bool('quiz_split_long_lines')) { + # very ugly, but required calculations depending on type of question + my $raw_crap = length(get_format('quiz_inc_question', '')); + my $msg_crap = $raw_crap; + if (!$quiz{inq}) { + my $suffix = ($quiz{type} == QT_MIX) ? '_x' : ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_fm' : ''); + my $answers = keys %{$quiz{lookup}}; + my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 + $msg_crap += length(get_format('quiz_msg_question' . $suffix, [$quiz{qnum}, $quiz{qcnt}, '', $answers, answers_str($answers), $duration])); + } else { + $msg_crap += length(get_format('quiz_msg_remind', '')); + } + my $cutoff = 497 - length($quiz{chan}{server}{nick} . $quiz{chan}{server}{userhost} . $quiz{chan}{name}); + my @words = split(/ /, $text); + $text = shift(@words); + my ($line, $subst) = (1, 1); + while (@words) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $ag = ' ' if ($_smarter_antigoogler && ($subst % ($_smarter_antigoogler_chunk + 1) == 0)); + my $word = shift(@words); + if (length($text . $ag . $word) > $cutoff - (($line == 1) ? $msg_crap : $raw_crap)) { + push(@lines, $text); + $text = $word; + $line++; + $subst = 1; + } else { + $text .= $ag . $word; + $subst++; + } + } + } else { + if ($_smarter_antigoogler) { + my @words = split(/ /, $text); + $text = shift(@words); + my $subst = 1; + while (@words) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $ag = ' ' if ($subst++ % ($_smarter_antigoogler_chunk + 1) == 0); + $text .= $ag . shift(@words); + } + } else { + while ($text =~ / /) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $text =~ s/ /$ag/; # one by one, not /g + } + } + } + push(@lines, $text); + return @lines; +} + +sub put_dots { + my ($hint, $format, $setting, $default, $marker) = @_; + my $char = settings_get_str($setting); #? substr(settings_get_str($setting), 0, 1) + my $dot = get_format($format, ($char eq '') ? $default : $char); + $hint =~ s/$marker/$dot/g; # le trick grande finale + my ($scol, $ecol) = ($dot =~ /^(\003\d{1,2}(?:,\d{1,2})?).(\003\d{1,2}(?:,\d{1,2})?)$/); + if (defined($scol) && defined($ecol)) { + $hint =~ s/$ecol $scol/ /g; # optimize color codes + $hint =~ s/$ecol$scol//g; + $hint =~ s/$ecol$//; + } + return $hint; +} + +sub make_hint { + my $dots_only = shift; + my @words = split(/ /, $quiz{data}[$quiz{qnum}]{answer}); + if (!@{$quiz{dots}}) { # make first dots + @quiz{qw/dcnt dmax lmax/} = (0) x 3; + my ($w, $dmax) = (0) x 2; + foreach my $word (@words) { + $quiz{lmax} = length($word) if (length($word) > $quiz{lmax}); + my ($l, $hword, $dcnt) = (0, '', 0); + foreach my $letter (split(//, $word)) { + if ($letter =~ /^[a-z0-9]$/i) { + push(@{$quiz{dots}[$w]}, $l); + $hword .= ($letter =~ /^[0-9]$/) ? "\002" : "\001"; # le trick (any ASCII non-printable char) + $quiz{dcnt}++; + $dcnt++; + } else { + $hword .= $letter; + } + $l++; + } + push(@{$quiz{hwords}}, $hword); + $dmax = $dcnt if ($dcnt > $dmax); + $w++; + } + $quiz{dmax} = $dmax; + } + return '' if ($dots_only); # prep dots only + $quiz{hnum}++; + my $first_dots = settings_get_bool('quiz_first_hint_dots'); + if (!$first_dots || ($quiz{hnum} > 1)) { # reveal some dots + my $random_hints = settings_get_bool('quiz_random_hints'); + my $random_but_first = settings_get_bool('quiz_nonrandom_first_hint') && ($quiz{hnum} == ($first_dots ? 2 : 1)); + my ($w, $dmax) = (0) x 2; + foreach my $r_wdots (@{$quiz{dots}}) { + if ((ref $r_wdots) && (@$r_wdots > 0)) { + my @letters = split(//, $words[$w]); + my @hletters = split(//, $quiz{hwords}[$w]); + my $sel = (!$random_hints || $random_but_first) ? 0 : int(rand(@$r_wdots)); + $hletters[@$r_wdots[$sel]] = $letters[@$r_wdots[$sel]]; + $quiz{hwords}[$w] = join('', @hletters); + splice(@$r_wdots, $sel, 1); + $quiz{dcnt}--; + $dmax = @$r_wdots if (@$r_wdots > $dmax); + } + $w++; + } + $quiz{dmax} = $dmax; + } + my $hint = join(' ', @{$quiz{hwords}}); + $hint = put_dots($hint, 'quiz_inc_hint_alpha', 'quiz_hint_alpha', $_hint_alpha, "\001"); + $hint = put_dots($hint, 'quiz_inc_hint_digit', 'quiz_hint_digit', $_hint_digit, "\002"); + return $hint; +} + +sub make_remind { + if (!$quiz{inq} || settings_get_bool('quiz_mix_on_remind')) { + if ($quiz{type} == QT_SCR) { + $quiz{data}[$quiz{qnum}]{question} = shuffle($quiz{data}[$quiz{qnum}]{realquestion}); + } elsif ($quiz{type} == QT_MIX) { + $quiz{data}[$quiz{qnum}]{question} = shuffle($quiz{data}[$quiz{qnum}]{answer}); + } + } + return antigoogle($quiz{data}[$quiz{qnum}]{question}); +} + +sub time_str { + my ($s, $mode) = @_; + my ($h, $m) = (0) x 2; + if ($mode == T_HMS) { + $h = int($s / 3600); + $m = int($s / 60) % 60; + $s %= 60; + } + my $str = ''; + $str .= get_format('quiz_inc_hours', $h) . ' ' if ($h); + $str .= get_format('quiz_inc_minutes', $m) . ' ' if ($m); + $str .= get_format('quiz_inc_seconds' . (($mode == T_MS) ? '_ms' : ''), $s) if ($s || (!$h && !$m)); + $str =~ s/ $//; + return $str; +} + +sub flex { + my ($value, $format, $flex) = (abs(shift), shift, 0); + my @flex = split(/\//, get_format($format)); + if ($value != 1) { + $flex++; + $flex++ if ($value =~ /^[2-4]$|[^1][2-4]$/); + } + return defined($flex[$flex]) ? $flex[$flex] : '???'; # just a precaution +} + +sub score_str { return flex(shift, 'quiz_flx_points'); } # X points +sub answers_str { return flex(shift, 'quiz_flx_answers'); } # X answers +sub aquestions_str { return flex(shift, 'quiz_flx_aquestions'); } # after X questions <- AFTER! +sub fquestions_str { return flex(shift, 'quiz_flx_fquestions'); } # from X questions <- FROM! + +sub percents { + my ($val, $of) = @_; + return ($of == 0) ? 0 : $val / $of * 100; +} + +sub stop_timer { + my $timer = shift; + if ($quiz{$timer}) { + timeout_remove($quiz{$timer}); + $quiz{$timer} = undef; + } +} + +sub stop_question { + @quiz{qw/inq hnum hprot rprot dcnt dmax lmax/} = (0) x 7; + stop_timer($_) foreach (qw/tround thint tremind twarn/); + $quiz{dots} = []; + $quiz{hwords} = []; + $quiz{lookup} = {}; +} + +sub stop_quiz { + stop_question(); + @quiz{qw/ison standby/} = (0) x 2; + stop_timer('tnext'); + signal_remove('message public', 'sig_pubmsg'); +} + +sub init_first_question { + my $delay = shift; + if ($delay > 0) { + $quiz{tnext} = timeout_add_once($delay, 'evt_next_question', undef); + } else { + evt_next_question(); + } +} + +sub init_next_question { + my ($msg, $instant) = @_; + if ($quiz{qnum} >= $quiz{qcnt}) { + send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_last'), $instant); + } else { + my $delay = abs(settings_get_int('quiz_delay' . ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_long' : ''))); # abs in case of <0 + $delay ||= ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? $_next_delay_long : $_next_delay); # in case of ==0 + send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_next' . (($quiz{type} == QT_MIX) ? '_x' : ''), $delay), $instant); + $quiz{tnext} = timeout_add_once($delay * 1000, 'evt_next_question', undef); + } +} + +sub name_to_type { + my ($name, $type) = (shift, undef); + return $name if ($name =~ /^\d+$/); + my %type = (diz => 1, std => 1, sta => 1, nrm => 1, nor => 1, zwy => 1, + mie => 2, mix => 2, lit => 2, + fam => 3, dru => 3, tea => 3, + mul => 4, all => 4, wsz => 4, bez => 4, + pom => 5, scr => 5); + foreach my $key (keys %type) { + $type = $type{$key}, last if (lc($name) =~ /^$key/i); + } + return $type; +} + +sub is_valid_data { + return (($quiz{qcnt} < 1) || + ((($quiz{type} == QT_STD) || ($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL) || + ($quiz{type} == QT_SCR)) && ($quiz{qcnt} * 2 != shift))) ? 0 : 1; +} + +sub correct_answer { + my ($addr, $nick, $timestamp, $points, $answer) = @_; + @{$quiz{players}{$addr}}{qw/besttime bestspeed/} = (0) x 2 if (!exists $quiz{players}{$addr}); + @{$quiz{players}{$addr}}{qw/nick timestamp/} = ($nick, $timestamp); + my $time = $timestamp - $quiz{qtime}; + my $speed = length($answer) / $time; + $quiz{players}{$addr}{alltime} += $time; + $quiz{players}{$addr}{allspeed} += $speed; + $quiz{players}{$addr}{besttime} = $time if (($quiz{players}{$addr}{besttime} == 0) || ($quiz{players}{$addr}{besttime} > $time)); + $quiz{players}{$addr}{bestspeed} = $speed if (($quiz{players}{$addr}{bestspeed} == 0) || ($quiz{players}{$addr}{bestspeed} < $speed)); + $quiz{players}{$addr}{score} += $points; + $quiz{players}{$addr}{answers}++; + $quiz{score} += $points; + $quiz{answers}++; + if ($quiz{type} == QT_FAM) { + $quiz{players}{$addr}{team} = 0 if (!exists $quiz{players}{$addr}{team}); # team_play is on and player is an outsider + my $team = $quiz{players}{$addr}{team}; + $quiz{teams}[$team]{score} += $points; + $quiz{teams}[$team]{answers}++; + } +} + +sub hcmd { + return sprintf(' %-37s - ', shift); +} + +sub hvar { + my ($setting, $type) = @_; + if ($type == V_INT) { + return sprintf(' %-26s : %-3d - ', $setting, settings_get_int($setting)); + } elsif ($type == V_BOOL) { + return sprintf(' %-26s : %-3s - ', $setting, settings_get_bool($setting) ? 'on' : 'off'); + } elsif ($type == V_STR) { + return sprintf(' %-26s : %-3s - ', $setting, settings_get_str($setting)); + } +} + +sub show_help { + send_ui_raw("%_$IRSSI{name}%_ v$VERSION by wilk (quiz obecnie: " . ($quiz{ison} ? ($quiz{standby} ? 'oczekuje na uruchomienie' : 'trwa') : 'jest wylaczony') . ')'); + send_ui_raw('%_Dostepne polecenia:%_'); + send_ui_raw(hcmd("/qtype [1-$_quiz_types/nazwa]") . 'zmiana rodzaju quizu (bez parametru wybiera kolejny)'); + send_ui_raw(hcmd("/qteams <2-$_max_teams>") . 'zmiana liczby druzyn (tylko Familiada)'); + send_ui_raw(hcmd("/qon [kanal] <plik> [1-$_quiz_types/nazwa] [0-$_max_teams]") . 'rozpoczecie quizu; mozna podac rodzaj quizu i liczbe druzyn'); + send_ui_raw(hcmd('/qstats [miejsca]') . 'wyswietla ranking graczy (Familiada: 0 - pokazuje tylko druzyny)'); + send_ui_raw(hcmd('/qhint') . 'wyswietlenie podpowiedzi (nie w Familiadzie/Multi)'); + send_ui_raw(hcmd('/qremind') . 'przypomnienie pytania'); + send_ui_raw(hcmd('/qskip') . 'pominiecie biezacego pytania'); + send_ui_raw(hcmd('/qoff') . 'przerwanie lub zakonczenie quizu'); + send_ui_raw(hcmd('/qdelay <sekundy>') . 'zmiana opoznienia miedzy pytaniami'); + send_ui_raw(hcmd('/qtime <sekundy>') . 'zmiana czasu trwania rundy (tylko Familiada/Multi)'); + send_ui_raw(hcmd('/qreload') . 'ponowne wczytanie pliku z pytaniami'); + send_ui_raw(hcmd('/qinit') . 'resetuje wszystkie ustawienia do wartosci poczatkowych'); + send_ui_raw('%_Dostepne ustawienia (/set):%_'); + send_ui_raw(hvar('quiz_type', V_INT) . 'rodzaj quizu (1: Dizzy, 2: Mieszacz/Literaki, 3: Familiada, 4: Multi (Familiada bez druzyn), 5: Pomieszany)'); + send_ui_raw(hvar('quiz_teams', V_INT) . "liczba druzyn (2-$_max_teams; tylko Familiada)"); + send_ui_raw(hvar('quiz_delay', V_INT) . 'opoznienie miedzy pytaniami (sek.)'); + send_ui_raw(hvar('quiz_delay_long', V_INT) . 'opoznienie miedzy pytaniami (sek.; tylko Familiada/Multi)'); + send_ui_raw(hvar('quiz_round_duration', V_INT) . 'czas trwania rundy (sek.; tylko Familiada/Multi)'); + send_ui_raw(hvar('quiz_max_hints', V_INT) . 'maksymalna liczba podpowiedzi (0: bez ograniczen, >0: limit podpowiedzi, <0: limit ukrytych znakow; nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_words_style', V_INT) . 'styl wyrazow (0: bez zmian, 1: male, 2: DUZE, 3: Kapitaliki; tylko Mieszacz/Pomieszany)'); + send_ui_raw(hvar('quiz_anticheat_delay', V_INT) . 'czas trwania ochrony !podp/!przyp (sek.; 0: wylaczone)'); + send_ui_raw(hvar('quiz_first_anticheat_delay', V_INT) . 'czas trwania ochrony pierwszego !podp/!przyp (sek.; 0: wylaczone)'); + send_ui_raw(hvar('quiz_points_per_answer', V_INT) . 'punkty za poprawna odpowiedz'); + send_ui_raw(hvar('quiz_min_points', V_INT) . 'minimum punktowe (tylko Familiada/Multi)'); + send_ui_raw(hvar('quiz_max_points', V_INT) . 'maksimum punktowe (tylko Familiada/Multi)'); + send_ui_raw(hvar('quiz_scoring_mode', V_INT) . 'tryb punktowania (1: ppa, 2: ppa++, 3: ppa++:max, 4: min++ppa, 5: min++ppa:max, 6: max--ppa:min, 7: max->min; tylko Familiada/Multi)'); + send_ui_raw(hvar('quiz_ranking_type', V_INT) . 'rodzaj rankingu (1: zwykly "1234", 2: zwarty "1223", 3: turniejowy "1224")'); + send_ui_raw(hvar('quiz_antigoogler', V_BOOL) . 'uzywac antygooglera do maskowania pytan?'); + send_ui_raw(hvar('quiz_split_long_lines', V_BOOL) . 'dzielic dlugie linie na czesci (nowsze irssi potrafi samo)?'); + send_ui_raw(hvar('quiz_show_first_hint', V_BOOL) . 'pokazywac podpowiedz razem z pytaniem? (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_first_hint_dots', V_BOOL) . 'pierwsza podpowiedz jako same kropki? (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_random_hints', V_BOOL) . 'losowe odslanianie podpowiedzi? albo od lewej do prawej (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_nonrandom_first_hint', V_BOOL) . 'losowe odslanianie podpowiedzi, poza pierwsza? (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_hint_alpha', V_STR) . 'znak podstawiany w podpowiedziach za litery (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_hint_digit', V_STR) . 'znak podstawiany w podpowiedziach za cyfry (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_words_mode', V_BOOL) . 'mieszac slowa osobno? albo wszystko razem (tylko Mieszacz)'); + send_ui_raw(hvar('quiz_smart_mix', V_BOOL) . 'mieszac kotwiczac cyfry i niektore znaki interpunkcyjne? (tylko Pomieszany)'); + send_ui_raw(hvar('quiz_smart_mix_chars', V_STR) . 'te znaki będą zakotwiczone (regex; tylko Pomieszany)'); + send_ui_raw(hvar('quiz_mix_on_remind', V_BOOL) . 'mieszac litery przy kazdym !przyp? (tylko Mieszacz/Pomieszany)'); + send_ui_raw(hvar('quiz_strict_match', V_BOOL) . 'tylko doslowne odpowiedzi? albo *dopasowane* (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_join_anytime', V_BOOL) . 'wchodzenie do druzyn w dowolnej chwili? (tylko Familiada)' ); + send_ui_raw(hvar('quiz_team_play', V_BOOL) . 'graja tylko gracze z druzyn? (tylko Familiada)'); + send_ui_raw(hvar('quiz_transfer_points', V_BOOL) . 'wraz ze zmiana druzyny przenosic punkty? (tylko Familiada)'); + send_ui_raw(hvar('quiz_limiter', V_BOOL) . 'limitowac najlepsza osobe do 50%+1 punktow? (nie dla Familiady/Multi)'); + send_ui_raw(hvar('quiz_keep_scores', V_BOOL) . 'sumowac punkty z poprzednich quizow?'); + send_ui_raw(hvar('quiz_cmd_hint', V_BOOL) . 'polecenie !podp jest dostepne dla graczy?'); + send_ui_raw(hvar('quiz_cmd_remind', V_BOOL) . 'polecenie !przyp jest dostepne dla graczy?'); +} + +##### Commands' handlers ##### +sub cmd_start { + if ($quiz{standby}) { + $quiz{standby} = 0; + init_first_question($_standby_delay); + return; + } + send_ui('quiz_err_ison'), return if ($quiz{ison}); + my ($args, $r_server, $window) = @_; + send_ui('quiz_err_server'), return if (!$r_server || !$r_server->{connected}); + my ($chan, $file, $type, $teams) = split(/ /, $args); + ($file, $chan) = ($chan, active_win()->{active}->{name}) if (!defined $file); # single arg call + send_ui('quiz_err_channel'), return if (!$chan || !$r_server->ischannel($chan)); + { + { package Irssi::Nick; } # should prevent irssi bug: "Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at ..." + $quiz{chan} = $r_server->channel_find($chan); + } + send_ui('quiz_err_nochannel', $chan), return if (!$quiz{chan}); + $file = (glob $file)[0]; # open() does not support "~" + send_ui('quiz_err_filename'), return if (!$file); + send_ui('quiz_err_nofile', $file), return if (!-e $file); + $type = defined($type) ? name_to_type($type) : settings_get_int('quiz_type'); + send_ui('quiz_err_type'), return if (!$type || ($type < 0) || ($type > $_quiz_types)); + if (defined $teams) { + send_ui('quiz_err_type'), return if (($type != QT_FAM) && ($type != QT_MUL)); + if (($type == QT_MUL) && ($teams >= 2)) { + $type = QT_FAM; + } elsif (($type == QT_FAM) && ($teams < 2)) { + $type = QT_MUL; + } + } else { + $teams = settings_get_int('quiz_teams'); + } + send_ui('quiz_err_teams'), return if (($type == QT_FAM) && (($teams !~ /^\d+$/) || ($teams < 2) || ($teams > $_max_teams))); + settings_set_int('quiz_type', $type); + settings_set_int('quiz_teams', $teams) if ($teams >= 2); + @quiz{qw/type tcnt file/} = ($type, $teams, $file); + my $lines = load_quiz($file); + send_ui('quiz_err_file', $file), return if (!is_valid_data($lines)); + if (!settings_get_bool('quiz_keep_scores')) { + $quiz{players} = {}; + $quiz{teams} = []; + @quiz{qw/score answers/} = (0) x 2; + } else { + #delete $quiz{players}{$_}{team} for (keys %{$quiz{players}}); #? unsure... + } + send_irc('quiz_msg_start1', INSTANT); + send_irc('quiz_msg_start2' . (($type == QT_FAM) ? '_f' : (($type == QT_MUL) ? '_m' : '')), $teams, INSTANT); + @quiz{qw/stime qnum ison/} = (time(), 0, 1); + if ($type == QT_FAM) { + $quiz{standby} = 1; + @{$quiz{teams}[$_]}{qw/score answers/} = (0) x 2 for (0 .. $teams); + } else { + $quiz{standby} = 0; + init_first_question($_start_delay); + } + signal_add_last('message public', 'sig_pubmsg'); +} + +sub cmd_stats { + send_ui('quiz_err_isoff'), return if (($quiz{score} == 0) && !$quiz{ison}); + send_ui('quiz_err_nochannel'), return if (!$quiz{chan}); + my $num = shift; + send_ui('quiz_err_ranking'), return if (($num ne '') && ($num !~ /^\d+$/)); + $num = -1 if ($num eq ''); + send_irc('quiz_msg_noscores'), return if (!keys %{$quiz{players}}); + my $qnum = $quiz{qnum}; + $qnum-- if ($quiz{inq}); + send_irc('quiz_msg_scores', [ + time_str(time() - $quiz{stime}, T_HMS), + $qnum, aquestions_str($qnum), + $quiz{qcnt}, fquestions_str($quiz{qcnt})]) if (!$quiz{standby}); + my $suffix = ''; + $suffix = '_full' if ((settings_get_int('quiz_points_per_answer') != 1) || + ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) && (settings_get_int('quiz_scoring_mode') != 1))); + if ($quiz{type} == QT_FAM) { + my @teams; + push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); + foreach my $team (1 .. $quiz{tcnt}) { + my ($score, $answers) = @{$quiz{teams}[$team]}{qw/score answers/}; + send_irc('quiz_msg_team_score' . $suffix, [ + $team, + (!defined $teams[$team]) ? '' : join(', ', @{$teams[$team]}), + $score, score_str($score), percents($score, $quiz{score}), + $answers, answers_str($answers), percents($answers, $quiz{answers})]); + } + } + return if ($quiz{standby} || (($num == 0) && ($quiz{type} == QT_FAM))); + my ($rank, $place, $exaequo, $prev, $ranking) = (0, 1, 0, undef, settings_get_int('quiz_ranking_type')); + $ranking = (($ranking < 1) || ($ranking > 3)) ? 1 : $ranking; + foreach my $player (sort { + $quiz{players}{$b}{score} <=> $quiz{players}{$a}{score} or + $quiz{players}{$b}{answers} <=> $quiz{players}{$a}{answers} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + my ($score, $answers) = @{$quiz{players}{$player}}{qw/score answers/}; + if (!defined($prev) || ($ranking == 1) || ($score != $prev)) { # 1234 + $rank += 1 + $exaequo; + $exaequo = 0; + $prev = $score; + } else { + if ($ranking == 3) { # 1224 + $exaequo++; + } elsif ($ranking == 2) { # 1223 + # nop + } else { # 1234 / fallback + $rank++; + } + } + last if ($_qstats_ranks && ($num > 0) && ($rank > $num)); + send_irc('quiz_msg_scores_place' . $suffix, [ + $rank, + $quiz{players}{$player}{nick}, + $score, score_str($score), percents($score, $quiz{score}), + $answers, answers_str($answers), percents($answers, $quiz{answers}), + $quiz{players}{$player}{besttime}, ($answers > 0) ? $quiz{players}{$player}{alltime} / $answers : 0, + $quiz{players}{$player}{bestspeed}, ($answers > 0) ? $quiz{players}{$player}{allspeed} / $answers : 0, + ($rank < 10) ? ' ' : '']); + last if (!$_qstats_ranks && ($place == $num)); + $place++; + } + return if ($num != -1); + $place = 1; + my @nicks; + foreach my $player (sort { + $quiz{players}{$a}{besttime} <=> $quiz{players}{$b}{besttime} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{besttime}])); + last if ($place >= $_qstats_records); + $place++; + } + send_irc('quiz_msg_scores_times', join(', ', @nicks)) if (@nicks); + $place = 1; + @nicks = (); + foreach my $player (sort { + $quiz{players}{$b}{bestspeed} <=> $quiz{players}{$a}{bestspeed} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{bestspeed}])); + last if ($place >= $_qstats_records); + $place++; + } + send_irc('quiz_msg_scores_speeds', join(', ', @nicks)) if (@nicks); +} + +sub cmd_delay { + my $delay = shift; + send_ui('quiz_err_delay'), return if (($delay !~ /^\d+$/) || ($delay < 1)); + my $type = $quiz{ison} ? $quiz{type} : settings_get_int('quiz_type'); + settings_set_int('quiz_delay' . ((($type == QT_FAM) || ($type == QT_MUL)) ? '_long' : ''), $delay); + send_irc('quiz_msg_delay', $delay) if ($quiz{ison}); + send_ui('quiz_inf_delay', $delay); +} + +sub cmd_time { + my $duration = shift; + #? send_ui('quiz_err_na'), return if (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL)); + send_ui('quiz_err_duration'), return if (($duration !~ /^\d+$/) || ($duration < 1)); + settings_set_int('quiz_round_duration', $duration); + send_irc('quiz_msg_duration', $duration) if ($quiz{ison}); + send_ui('quiz_inf_duration', $duration); +} + +sub cmd_teams { + my $teams = shift; + #? send_ui('quiz_err_na'), return if (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL)); + send_ui('quiz_err_ison'), return if ($quiz{ison}); + send_ui('quiz_err_teams'), return if (($teams !~ /^\d+$/) || ($teams < 2) || ($teams > $_max_teams)); + settings_set_int('quiz_teams', $teams); + send_ui('quiz_inf_teams', $teams); +} + +sub cmd_type { + send_ui('quiz_err_ison'), return if ($quiz{ison}); + my $type = shift; + if ($type ne '') { + $type = name_to_type($type); + send_ui('quiz_err_type'), return if (!$type || ($type < 1) || ($type > $_quiz_types)); + } else { + $type = (settings_get_int('quiz_type') % $_quiz_types) + 1; + } + settings_set_int('quiz_type', $type); + send_ui('quiz_inf_type', ('Dizzy', 'Mieszacz/Literaki', 'Familiada', 'Multi (Familiada bez druzyn)', 'Pomieszany')[$type - 1]); +} + +sub cmd_skip { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + stop_question(); + init_next_question(get_format('quiz_msg_skipped')); +} + +sub cmd_hint { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_na'), return if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + send_irc('quiz_msg_hint', make_hint()); +} + +sub cmd_remind { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_na'), return if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + send_irc('quiz_msg_remind', get_format('quiz_inc_question', $text)); + } else { + send_irc('quiz_inc_question', $text); + } + } +} + +sub cmd_stop { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + stop_quiz(); + send_irc('quiz_msg_stop1'); + send_irc('quiz_msg_stop2', [$quiz{qnum}, time_str(time() - $quiz{stime}, T_HMS)]); +} + +sub cmd_init { + settings_set_int($_, $settings_int{$_}) for (keys %settings_int); + settings_set_bool($_, $settings_bool{$_}) for (keys %settings_bool); + settings_set_str($_, $settings_str{$_}) for (keys %settings_str); + send_ui('quiz_inf_reset'); +} + +sub cmd_reload { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + my $cnt = $quiz{qcnt}; + my $lines = load_quiz($quiz{file}); + if (is_valid_data($lines)) { + send_ui(($quiz{qcnt} != $cnt) ? 'quiz_wrn_reload' : 'quiz_inf_reload'); + if ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) && $quiz{inq}) { + %{$quiz{lookup}} = map { lc($_) => $_ } keys %{$quiz{data}[$quiz{qnum}]{answers}}; + } + } else { + stop_quiz(); + send_irc('quiz_msg_stop1'); + send_irc('quiz_msg_stop2', [$quiz{qnum}, time_str(time() - $quiz{stime}, T_HMS)]); + send_ui('quiz_err_file', $quiz{file}); + } +} + +sub cmd_help { + show_help(); +} + +sub cmd_irssi_help { + my $cmd = shift; + if ($cmd =~ /^i?quiz$/) { + show_help(); + signal_stop(); + } +} + +##### Timers' events ##### +sub evt_delayed_show_msg { + my ($msg) = @_; + signal_emit('message own_public', $quiz{chan}{server}, $msg, $quiz{chan}{name}); +} + +sub evt_delayed_show_notc { + my $ref = shift; + my ($msg, $nick) = @{$ref}; + signal_emit('message irc own_notice', $quiz{chan}{server}, $msg, $nick); +} + +sub evt_delayed_load_info { + send_ui('quiz_inf_start'); +} + +sub evt_next_question { + $quiz{qtime} = time(); + $quiz{qnum}++; + my $suffix = ''; + if ($quiz{type} == QT_MIX) { + $suffix = '_x'; + } elsif (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + %{$quiz{lookup}} = map { lc($_) => $_ } keys %{$quiz{data}[$quiz{qnum}]{answers}}; + $suffix = '_fm'; + } + my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + my $answers = keys %{$quiz{lookup}}; + send_irc('quiz_msg_question' . $suffix, [ + $quiz{qnum}, $quiz{qcnt}, + get_format('quiz_inc_question', $text), + $answers, answers_str($answers), + $duration], INSTANT); + } else { + send_irc('quiz_inc_question', $text, INSTANT); #? not INSTANT? + } + } + if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + $quiz{tround} = timeout_add_once($duration * 1000, 'evt_round_timeout', undef); + if (($_round_warn_time > 0) && ($duration > $_round_warn_time * $_round_warn_coeff)) { + $quiz{twarn} = timeout_add_once(($duration - $_round_warn_time) * 1000, 'evt_round_timeout_warn', undef); + } + } else { + send_irc('quiz_msg_hint', make_hint()) if (settings_get_bool('quiz_show_first_hint')); + } + $quiz{inq} = 1; + my $delay = settings_get_int('quiz_first_anticheat_delay'); + if ($delay > 0) { + $quiz{hprot} = 1; + $quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); + if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { + $quiz{rprot} = 1; + $quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); + } + } +} + +sub evt_round_timeout_warn { + send_irc('quiz_msg_warn_timeout', $_round_warn_time); +} + +sub evt_round_timeout { + stop_question(); + init_next_question(get_format('quiz_msg_timeout')); #? INSTANT? +} + +##### User interaction - responses / handlers ##### +sub show_score { + my ($nick, $addr, $who) = @_; + if ($who && (lc($nick) ne lc($who))) { + my $found = 0; + foreach my $player (keys %{$quiz{players}}) { + if (lc($quiz{players}{$player}{nick}) eq lc($who)) { + send_irc('quiz_msg_score_other', [$quiz{players}{$player}{nick}, $quiz{players}{$player}{score}, score_str($quiz{players}{$player}{score})]); + $found++; + last; + } + } + send_irc('quiz_msg_noscore_other', $who) if (!$found); + } else { + if (exists $quiz{players}{$addr}) { + send_irc('quiz_msg_score', [$nick, $quiz{players}{$addr}{score}, score_str($quiz{players}{$addr}{score})]); + } else { + send_irc('quiz_msg_noscore', $nick); + } + } +} + +sub join_team { + my ($nick, $addr, $team) = @_; + return unless (($quiz{type} == QT_FAM) && (settings_get_bool('quiz_join_anytime') || $quiz{standby})); + return unless (($team >= 1) && ($team <= $quiz{tcnt})); + if (exists $quiz{players}{$addr}) { + if (settings_get_bool('quiz_transfer_points')) { + my ($score, $answers) = @{$quiz{players}{$addr}}{qw/score answers/}; + if (exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0)) { # not an outsider + my $from = $quiz{players}{$addr}{team}; + $quiz{teams}[$from]{score} -= $score; + $quiz{teams}[$from]{answers} -= $answers; + } + $quiz{teams}[$team]{score} += $score; + $quiz{teams}[$team]{answers} += $answers; + } + $quiz{players}{$addr}{team} = $team; + } else { + @{$quiz{players}{$addr}}{qw/nick timestamp team/} = ($nick, time(), $team); + @{$quiz{players}{$addr}}{qw/score answers besttime alltime bestspeed allspeed/} = (0) x 6; + } + my @teams; + push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); + send_irc_whisper('quiz_msg_team_join', [$team, join(', ', @{$teams[$team]})], $nick) if (defined $teams[$team]); +} + +sub show_hint { + return unless (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL) && settings_get_bool('quiz_cmd_hint')); + return if ($quiz{hprot}); + my $hints_limit = settings_get_int('quiz_max_hints'); + make_hint(PREPDOTS) if (!@{$quiz{dots}} && ($hints_limit < 0)); + if (($hints_limit == 0) || + (($hints_limit > 0) && ($quiz{hnum} < $hints_limit)) || + (($hints_limit < 0) && ($quiz{dmax} > abs($hints_limit)))) { + send_irc('quiz_msg_hint', make_hint()); + my $delay = settings_get_int('quiz_anticheat_delay'); + if ($delay > 0) { + $quiz{hprot} = 1; + $quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); + } + } +} + +sub show_remind { + return unless (settings_get_bool('quiz_cmd_remind')); + if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { + return if ($quiz{rprot}); + my $delay = settings_get_int('quiz_anticheat_delay'); + if ($delay > 0) { + $quiz{rprot} = 1; + $quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); + } + } + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + send_irc('quiz_msg_remind', get_format('quiz_inc_question', $text)); + } else { + send_irc('quiz_inc_question', $text); + } + } +} + +sub check_answer { + my ($nick, $addr, $answer) = @_; + if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + return unless (exists($quiz{lookup}{$answer}) && ($quiz{data}[$quiz{qnum}]{answers}{$quiz{lookup}{$answer}} > 0)); + return unless (($quiz{type} == QT_MUL) || !settings_get_bool('quiz_team_play') || (exists($quiz{players}{$addr}) && exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0))); # last condition: for non team players there is no record + my ($time, $match) = (time(), $quiz{lookup}{$answer}); + my $answers = keys %{$quiz{data}[$quiz{qnum}]{answers}}; + my $id = $quiz{data}[$quiz{qnum}]{answers}{$match}; + my $value = $answers - $id + 1; + my $points = settings_get_int('quiz_points_per_answer'); # ppa + my $min = settings_get_int('quiz_min_points'); + my $max = settings_get_int('quiz_max_points'); + my $mode = settings_get_int('quiz_scoring_mode'); + if ($mode == 2) { # ppa++ + $points *= $value; + } elsif ($mode == 3) { # ppa++:max + $points *= $value; + $points = $max if ($points > $max); + } elsif ($mode == 4) { # min++ppa + ($points *= $value - 1) += $min; + } elsif ($mode == 5) { # min++ppa:max + ($points *= $value - 1) += $min; + $points = $max if ($points > $max); + } elsif ($mode == 6) { # max--ppa:min + $points = $max - $points * ($id - 1); + $points = $min if ($points < $min); + } elsif ($mode == 7) { # max->min + $points = int(($value - 1) * ($max - $min) / ($answers - 1) + $min + 0.5); + #} elsif ($mode == 8) { # max%:min + # $points = int($max * $value / $answers + 0.5); + # $points = $min if ($points < $min); + } + correct_answer($addr, $nick, $time, $points, $answer); + send_irc('quiz_msg_congrats', [ + $nick, + ($points == 1) ? get_format('quiz_inc_got_point', score_str($points)) : get_format('quiz_inc_got_points', [$points, score_str($points)]), + $match, + $time - $quiz{qtime}, + length($answer) / ($time - $quiz{qtime}), + $quiz{players}{$addr}{score}]); + $quiz{data}[$quiz{qnum}]{answers}{$match} *= -1; + if (!grep { $_ > 0 } values %{$quiz{data}[$quiz{qnum}]{answers}}) { + stop_question(); + init_next_question(get_format('quiz_msg_all_answers')); #? not INSTANT + } + } else { + return unless (($answer eq lc($quiz{data}[$quiz{qnum}]{answer})) || + (!settings_get_bool('quiz_strict_match') && (index($answer, lc $quiz{data}[$quiz{qnum}]{answer}) >= 0))); + my ($time, $points) = (time(), settings_get_int('quiz_points_per_answer')); + return unless (!settings_get_bool('quiz_limiter') || !exists($quiz{players}{$addr}) || + ($quiz{players}{$addr}{score} < int($quiz{qcnt} * 0.5 + 1) * $points)); # 50%+1 + stop_question(); + correct_answer($addr, $nick, $time, $points, $answer); + init_next_question(get_format('quiz_msg_congrats', [ + $nick, + ($points == 1) ? get_format('quiz_inc_got_point', score_str($points)) : get_format('quiz_inc_got_points', [$points, score_str($points)]), + $quiz{data}[$quiz{qnum}]{answer}, + $time - $quiz{qtime}, + length($answer) / ($time - $quiz{qtime}), + $quiz{players}{$addr}{score}]), INSTANT); + } +} + +##### Signals' handlers ##### +sub sig_pubmsg { + my ($r_server, $msg, $nick, $addr, $target) = @_; + return if (!$quiz{ison} || ($r_server->{tag} ne $quiz{chan}{server}{tag}) || (lc($target) ne lc($quiz{chan}{name}))); + for ($msg) { + tr/\t/ /; # tabs to spaces + s/ {2,}/ /g; # fix double spaces + s/^ +| +$//g; # trim leading/trailing spaces + s/\002|\003(?:\d{1,2}(?:,\d{1,2})?)?|\017|\026|\037//g; # remove formatting + # \002 - bold \003$fg(,$bg)? - color \017 - plain \026 - reverse \037 - underline + } + return if ($msg eq ''); + my $lmsg = lc $msg; + if ($lmsg =~ /^!ile(?:\s+([^\s]+))?/) { + show_score($nick, $addr, $1) + } elsif ($lmsg =~ /^!join\s+(\d)$/) { + join_team($nick, $addr, $1); + } + return if (!$quiz{inq}); + if ($lmsg eq '!podp') { + show_hint(); + } elsif (($lmsg eq '!przyp') || ($lmsg eq '!pyt')) { + show_remind(); + } + check_answer($nick, $addr, $lmsg); +} + +##### Bindings ##### +command_bind('help', 'cmd_irssi_help'); +command_bind('quiz', 'cmd_help'); +command_bind('qtype', 'cmd_type'); +command_bind('qteams', 'cmd_teams'); +command_bind('qon', 'cmd_start'); +command_bind('qdelay', 'cmd_delay'); +command_bind('qtime', 'cmd_time'); +command_bind('qhint', 'cmd_hint'); +command_bind('qremind', 'cmd_remind'); +command_bind('qskip', 'cmd_skip'); +command_bind('qstats', 'cmd_stats'); +command_bind('qoff', 'cmd_stop'); +command_bind('qreload', 'cmd_reload'); +command_bind('qinit', 'cmd_init'); + +##### User settings ##### +settings_add_int($IRSSI{name}, $_, $settings_int{$_}) for (keys %settings_int); +settings_add_bool($IRSSI{name}, $_, $settings_bool{$_}) for (keys %settings_bool); +settings_add_str($IRSSI{name}, $_, $settings_str{$_}) for (keys %settings_str); + +##### Initialization ##### +timeout_add_once($_display_delay, 'evt_delayed_load_info', undef); # le trick (workaround for info showing before script load message) diff --git a/scripts/iquiz_en.pl b/scripts/iquiz_en.pl new file mode 100644 index 0000000..14b55f5 --- /dev/null +++ b/scripts/iquiz_en.pl @@ -0,0 +1,1261 @@ +################################################################## +## irssi Quiz (iQuiz) script (2010-2016) by wilk/xorandor ## +################################################################## +## Script inspired by classic mIRC scripts: "Dizzy" by Dizzy, ## +## "Mieszacz" & "Familiada" by snajperx (both with my later ## +## upgrades). ## +## Other credits: ## +## Bjoern 'fuchs' Krombholz for splitlong.pl calculations ## +################################################################## + +# Tested more or less with irssi 0.8.15 & 0.8.18 and Perl 5.8.8, 5.10.1, 5.14.2, 5.16.3 & 5.18.2 + +# Script works with: +# +# - standard Dizzy/Pomieszany files (also without "pyt"/"odp" prefixes): +# +# pyt Evaluate: 2+2=? +# odp four +# pyt Star closest to Earth? +# odp Sun +# ... +# +# - standard Mieszacz files (also without line numbers): +# +# 1 alpha +# 2 beta +# 3 gamma +# 4 delta +# ... +# +# - standard Familiada files (can have any number of answers per question, used also for Multi): +# +# Planets of our Solar System: +# Mercury*Venus*Earth*Mars*Jupiter*Saturn*Uranus*Neptune +# First six alkanes: +# methane*ethane*propane*butane*pentane*hexane +# ... + +# >>> To view all available commands and settings type: /quiz + +# only core modules +use strict; +use warnings; +use Irssi qw(theme_register current_theme command_bind settings_add_int settings_add_bool settings_add_str settings_get_int settings_get_bool settings_get_str settings_set_int settings_set_bool settings_set_str printformat timeout_add_once timeout_remove signal_add_last signal_remove signal_stop signal_emit active_win); +use Time::HiRes qw(time); +use constant { QT_STD => 1, QT_MIX => 2, QT_FAM => 3, QT_MUL => 4, QT_SCR => 5 }; # QT_MIL => 6, QT_FOR => 7 + +our $VERSION = '160919_en'; +our %IRSSI = ( + authors => 'wilk', + name => 'iQuiz', + description => 'irssi quiz script', # one script to bind them all + license => 'GNU GPL v3 or any later version', + changed => (($VERSION =~ /^(\d\d)(\d\d)(\d\d)/) ? "20$1-$2-$3" : $VERSION), + url => 'http://iquiz.quizpl.net', + contact => 'http://mail.quizpl.net', + changes => 'see http://www.quizpl.net/viewtopic.php?f=3&t=404', + usage => 'see http://www.quizpl.net/viewtopic.php?f=3&t=587' +); + +##### Hardcoded settings ##### +my $_display_delay = 100; # msec; workaround for display issue (response before request) +my $_start_delay = 5000; # msec; delay between /qon and showing first question (or 0) +my $_standby_delay = 1000; # msec; delay between /qon and showing first question (or 0) while on standby +my $_max_teams = 5; # int; max allowed teams (5 is reasonable) +my $_shuffle_watchdog = 10; # int; max shuffling repetitions to prevent mixed == original, but avoid infinite loop +my $_shuffle_threshold = 3; # int; below this length reshuffling is off (to prevent mixed == original) +my $_randomized_antigoogler = 0; # bool; use better, randomized antigoogler? (will increase question length) +my $_smarter_antigoogler = 1; # bool; use smarter antigoogler? (leaves some empty spaces for better line breaking) +my $_smarter_antigoogler_chunk = 2; # int; leaves empty space every after this many substitutions (for use with $_smarter_antigoogler) +my $_protect_urls = 1; # bool; turn off antigoogler if URL is detected in question? +my $_round_warn_time = 15; # sec; seconds before round end to show warning (0 = off) +my $_round_warn_coeff = 1.5; # float; round duration must be longer than coeff * $_round_warn_time to show warning (protection) +my $_qstats_ranks = 0; # bool; 0: /qstats param corresponds to number of players, 1: /qstats param corresponds to rank +my $_qstats_records = 5; # int; number of time/speed record places in /qstats + +my $_next_delay = 10; # sec; default delay between questions +my $_next_delay_long = 20; # sec; default delay between questions (fam/mul) (longer delay to prevent flooding and give a breath) +my $_round_duration = 90; # sec; default round duration +my $_hint_alpha = '.'; # char; default substitution symbol for alphabet characters in hints (special characters are left intact) +my $_hint_digit = '.'; # char; default substitution symbol for digit characters in hints (special characters are left intact) + +my $_quiz_types = 5; # (do not change) + +##### Internal stuff ##### +use constant { T_HMS => 0, T_S => 1, T_MS => 2 }; # 0: h/m/s, 1: s only, 2: s.ms +use constant { INSTANT => 1, PREPDOTS => 1, V_INT => 1, V_BOOL => 2, V_STR => 3 }; + +my %quiz = ( + chan => undef, file => '', + type => 0, tcnt => 0, # copies just in case someone modifies settings directly while quiz is running + ison => 0, inq => 0, standby => 0, + stime => 0, qtime => 0, + qcnt => 0, qnum => 0, hnum => 0, + score => 0, answers => 0, + tnext => undef, tround => undef, thint => undef, tremind => undef, twarn => undef, + hprot => 0, rprot => 0, + data => [], # data[]{question realquestion answer answers{}} + teams => [], # teams[]{score answers} + players => {}, # players{}{nick timestamp score answers team besttime alltime bestspeed allspeed} + lookup => {}, dcnt => 0, dmax => 0, lmax => 0, dots => [], hwords => [] +); + +my %settings_int = ( + 'quiz_type' => 1, + 'quiz_teams' => 2, + 'quiz_delay' => $_next_delay, + 'quiz_delay_long' => $_next_delay_long, + 'quiz_round_duration' => $_round_duration, + 'quiz_max_hints' => 0, + 'quiz_words_style' => 0, + 'quiz_anticheat_delay' => 3, + 'quiz_first_anticheat_delay' => 7, + 'quiz_points_per_answer' => 1, + 'quiz_min_points' => 1, + 'quiz_max_points' => 50, + 'quiz_scoring_mode' => 4, + 'quiz_ranking_type' => 3, +); + +my %settings_bool = ( + 'quiz_antigoogler' => 1, + 'quiz_split_long_lines' => 1, + 'quiz_show_first_hint' => 0, + 'quiz_first_hint_dots' => 0, + 'quiz_random_hints' => 1, + 'quiz_nonrandom_first_hint' => 1, + 'quiz_words_mode' => 1, + 'quiz_smart_mix' => 1, + 'quiz_mix_on_remind' => 1, + 'quiz_strict_match' => 1, + 'quiz_join_anytime' => 1, + 'quiz_team_play' => 1, + 'quiz_transfer_points' => 0, + 'quiz_limiter' => 0, + 'quiz_keep_scores' => 0, + 'quiz_cmd_hint' => 1, + 'quiz_cmd_remind' => 1, +); + +my %settings_str = ( + 'quiz_hint_alpha' => $_hint_alpha, + 'quiz_hint_digit' => $_hint_digit, + 'quiz_smart_mix_chars' => '\d()",.;:?!', +); + +##### Theme (only channel messages are localized by default, feel free to customize here or via /format, except authorship) ##### +# quiz_inf_*, quiz_wrn_* & quiz_err_* messages are irssi only - use irssi formatting and irssi color codes +# quiz_msg_* messages are sent on channel - use sprintf formatting and mIRC color codes: +# \002 - bold \003$fg(,$bg)? - color \017 - plain \026 - reverse \037 - underline +# quiz_inc_* - not sent directly, used as inclusions +# quiz_flx_* - not sent directly, words' inflections +# Important: To prevent visual glitches use two digit color codes! i.e. \00304 instead of \0034 +theme_register([ + 'quiz_inf_start', '%_iQuiz:%_ Type /quiz to get more help.', + 'quiz_inf_delay', '%_iQuiz:%_ %gChanged delay between questions to: %_$0%_s%n', + 'quiz_inf_duration', '%_iQuiz:%_ %gChanged round duration to: %_$0%_s%n', + 'quiz_inf_type', '%_iQuiz:%_ %gChanged quiz type to: %_$0%_%n', + 'quiz_inf_teams', '%_iQuiz:%_ %gChanged number of teams to: %_$0%_%n', + 'quiz_inf_reset', '%_iQuiz:%_ %gAll settings has been reset to default values%n', + 'quiz_inf_reload', '%_iQuiz:%_ %gFile reloaded%n', + + 'quiz_wrn_reload', '%_iQuiz:%_ %YQuestions\' count mismatch after reloading%n', + + 'quiz_err_ison', '%_iQuiz:%_ %RQuiz is already on%n', + 'quiz_err_isoff', '%_iQuiz:%_ %RQuiz is already off%n', + 'quiz_err_server', '%_iQuiz:%_ %RNot connected to server%n', + 'quiz_err_channel', '%_iQuiz:%_ %RInvalid channel%n', + 'quiz_err_nochannel', '%_iQuiz:%_ %RChannel "$0" is not open%n', + 'quiz_err_filename', '%_iQuiz:%_ %RInvalid filename%n', + 'quiz_err_nofile', '%_iQuiz:%_ %RFile "$0" not found%n', + 'quiz_err_file', '%_iQuiz:%_ %RFile "$0" seems to be corrupted%n', + 'quiz_err_argument', '%_iQuiz:%_ %RInvalid argument%n', + 'quiz_err_noquestion', '%_iQuiz:%_ %RWait until question is asked%n', + 'quiz_err_type', '%_iQuiz:%_ %RInvalid quiz type%n', + 'quiz_err_delay', '%_iQuiz:%_ %RInvalid delay between questions%n', + 'quiz_err_duration', '%_iQuiz:%_ %RInvalid round duration%n', + 'quiz_err_teams', '%_iQuiz:%_ %RInvalid number of teams%n', + 'quiz_err_ranking', '%_iQuiz:%_ %RInvalid number of players%n', + 'quiz_err_na', '%_iQuiz:%_ %RFeature is not available at this time%n', + + 'quiz_msg', '%s', # custom text + 'quiz_msg_start1', "\00303>>> \00310iQuiz by wilk - let's start \00303<<<", + 'quiz_msg_start2', "\00303Commands: !hint, !remind, !score, !score nick", + 'quiz_msg_start2_f', "\00303Commands: !remind, !score, !score nick, !join 1-%u", # 1: max teams + 'quiz_msg_start2_m', "\00303Commands: !remind, !score, !score nick", + 'quiz_msg_stop1', "\00303>>> \00310iQuiz by wilk - the end \00303<<<", + 'quiz_msg_stop2', "\00303Rounds: \00304%u \00303Play time: \00304%s", # 1: round, 2: time_str (hms) + 'quiz_msg_question', "\00303\037Question %u/%u:\037 %s", # see below + 'quiz_msg_question_x', "\00303\037Word %u/%u:\037 %s", # see below + 'quiz_msg_question_fm', "\00303\037Question %u/%u:\037 %s \00303(\00313%u\00303 %s, time: %us)", # 1: round, 2: rounds, 3: question (quiz_inc_question), 4: answers, 5: quiz_flx_answers, 6: round time (s) + 'quiz_inc_question', "\00300,01 %s \017", # 1: question (antygoogler takes first color code to harden question - must use background color if using antigoogler; if any color is used finish with "\017" to reset it) + 'quiz_msg_hint', "\00303Hint: \00304%s", # 1: hint + 'quiz_inc_hint_alpha', "\00310%s\00304", # 1: symbol (color codes are used to distinguish between hidden letter and real dot, but you may omit them) + 'quiz_inc_hint_digit', "\00310%s\00304", # 1: symbol (same as above) + 'quiz_msg_remind', "\00303Reminder: %s", # 1: question (quiz_inc_question) + 'quiz_msg_delay', "\00303Delay between questions: \00304%u\00303s", # 1: time (s) + 'quiz_msg_duration', "\00303Round duration: \00304%u\00303s", # 1: time (s) + 'quiz_msg_score', "\00304%s\00303\002\002, you have scored \00304%d\00303 %s so far.", # 1: nick, 2: score, 3: quiz_flx_points + 'quiz_msg_noscore', "\00304%s\00303\002\002, you haven't scored any point so far!", # 1: nick + 'quiz_msg_score_other', "\00304%s\00303 scored \00304%d\00303 %s so far.", # see quiz_msg_score + 'quiz_msg_noscore_other', "\00304%s\00303 hasn't scored any point so far!", # 1: nick + 'quiz_msg_noscores', "\00303Scoreboard is empty.", + 'quiz_msg_scores', "\00303Quiz scores after %s and %u %s:", # 1: time_str (hms), 2: question, 3: quiz_flx_questions, 4: questions (total), 5: quiz_flx_questions (total) + 'quiz_msg_scores_place', "\00303%u. place: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (avg. guessing time: %10\$.3fs)", # 1: place, 2: nick, 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers%, 9: best time, 10: avg time, 11: best speed, 12: avg speed, 13: spacer + 'quiz_msg_scores_place_full', "\00303%u. place: \00304%s\00303 - \00304%d\00303 %s [%.1f%%] (%u %s, avg. guessing time: %10\$.3fs)", # see quiz_msg_scores_place + 'quiz_msg_team_score', "\00303Team %u (%s): \00304%d\00303 %s", # 1: team, 2: players (comma separated), 3: score, 4: quiz_flx_points, 5: score%, 6: answers, 7: quiz_flx_answers, 8: answers% + 'quiz_msg_team_score_full', "\00303Team %u (%s): \00304%d\00303 %s (%6\$u %7\$s)", # see quiz_msg_team_score + 'quiz_msg_team_join', "\00303You have joined to Team %u (%s).", # 1: team, 2: players (comma separated) + 'quiz_inc_team_nick', "\00307%s\00303", # 1: nick + 'quiz_msg_scores_times', "\00303Fastest players (time): %s", # 1: players (comma separated) + 'quiz_msg_scores_speeds', "\00303Fastest players (ch/s): %s", # 1: players (comma separated) + 'quiz_inc_scores_record', "\00303%u. \00304%s\00303 (%.3f)", # 1: place, 2: nick, 3: time/speed record + 'quiz_msg_congrats', "\00303Congrats, \00304%s\00303! You get %s for answer \00304%s\00303 given after %.3fs (%.3f chars/s) - total points: \00304%d\00303.", # 1: nick, 2: quiz_inc_got_point*, 3: answer, 4: time (ms), 5: speed (chars/s), 6: total score + 'quiz_inc_got_points', "\00304%d\00303 %s", # 1: points, 2: quiz_flx_points + 'quiz_inc_got_point', "\00303a %s", # 1: quiz_flx_point + 'quiz_inc_hours', '%u hr', # 1: hours + 'quiz_inc_minutes', '%u min', # 1: minutes + 'quiz_inc_seconds', '%u sec', # 1: seconds + 'quiz_inc_seconds_ms', '%.3f sec', # 1: seconds.milliseconds + 'quiz_msg_warn_timeout', "\00307Warning, only \00304%u\00307s left for answering!", # 1: time (s) + 'quiz_msg_all_answers', "\00303All answers were guessed!", + 'quiz_msg_timeout', "\00303Timeout!", + 'quiz_msg_next', "\00303Next question after %us...", # 1: time (s) + 'quiz_msg_next_x', "\00303Next word after %us...", # 1: time (s) + 'quiz_msg_last', "\00307No more questions!", + 'quiz_msg_skipped', "\00303This question has been skipped.", + # 1 point / 1 punkt + # x points / x punktow + # 2-4, x2-x4 points (x != 1) / 2-4, x2-x4 punkty (x != 1) + 'quiz_flx_points', 'point/points/points', + # 1 answer / 1 odpowiedz + # x answers / x odpowiedzi + # 2-4, x2-x4 answers (x != 1) / 2-4, x2-x4 odpowiedzi (x != 1) + 'quiz_flx_answers', 'answer/answers/answers', + # after 1 question / po 1 pytaniu + # after x questions / po x pytaniach + # after 2-4, x2-x4 questions (x != 1) / po 2-4, x2-x4 pytaniach (x != 1) + 'quiz_flx_aquestions', 'question/questions/questions', + # from 1 question / z 1 pytania + # from x questions / z x pytan + # from 2-4, x2-x4 questions (x != 1) / z 2-4, x2-x4 pytan (x != 1) + 'quiz_flx_fquestions', 'question/questions/questions', +]); + +##### Support routines ##### +sub load_quiz { + my ($fname, $lines) = (shift, 0); + $quiz{data} = []; + $quiz{qcnt} = 0; + return 0 unless (open(my $fh, '<', $fname)); + while (<$fh>) { + s/[\n\r]//g; # chomp is platform dependent ($/) + tr/\t/ /; # tabs to spaces + s/ {2,}/ /g; # fix double spaces + s/^ +| +$//g; # trim leading/trailing spaces/tabs + next if (/^ *$/); + if (($quiz{type} == QT_STD) || ($quiz{type} == QT_SCR)) { + if ($lines % 2) { + s/^o(dp|pd) //i; # remove format (broken as well) + $quiz{data}[++$quiz{qcnt}]{answer} = $_; # ++ only on complete question + } else { + s/^p(yt|ty) //i; # remove format (broken as well) + $quiz{data}[$quiz{qcnt} + 1]{($quiz{type} == QT_STD) ? 'question' : 'realquestion'} = $_; + } + } elsif ($quiz{type} == QT_MIX) { + s/^\d+ //; # remove format + $quiz{data}[++$quiz{qcnt}]{answer} = $_; + } elsif (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + if ($lines % 2) { + s/ +\*/*/g; # fix format + s/\* +/*/g; # fix format + my $enum = 1; + # ++ only on complete question + %{$quiz{data}[++$quiz{qcnt}]{answers}} = map { $_ => $enum++ } split /\*/; + } else { + $quiz{data}[$quiz{qcnt} + 1]{question} = $_; + } + } + $lines++; + } + close($fh); + return $lines; +} + +sub get_format { + my ($format, $args) = @_; + return sprintf(current_theme()->get_format(__PACKAGE__, $format), ref($args) ? @{$args} : (defined($args) ? $args : ())); +} + +sub send_ui { + my ($format, @rest) = @_; + printformat(MSGLEVEL_CRAP, $format, @rest); +} + +sub send_ui_raw { + print CLIENTCRAP shift; +} + +sub send_irc { + my (undef, undef, $instant) = @_; + my $msg = get_format(@_); + if ($quiz{chan}{server}{connected}) { + if ($instant) { # instant or queued + $quiz{chan}{server}->send_raw_now("PRIVMSG $quiz{chan}{name} :$msg"); + } else { + $quiz{chan}{server}->send_raw("PRIVMSG $quiz{chan}{name} :$msg"); + } + timeout_add_once($_display_delay, 'evt_delayed_show_msg', $msg); # le trick (workaround for chantext showing after owntext) + } else { + send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats + send_ui('quiz_err_server'); + } +} + +sub send_irc_whisper { + my (undef, undef, $nick, $instant) = @_; + my $msg = get_format(@_); + if ($quiz{chan}{server}{connected}) { + if ($instant) { # instant or queued + $quiz{chan}{server}->send_raw_now("NOTICE $nick :$msg"); + } else { + $quiz{chan}{server}->send_raw("NOTICE $nick :$msg"); + } + timeout_add_once($_display_delay, 'evt_delayed_show_notc', [$msg, $nick]); # le trick (workaround for chantext showing after owntext) + } else { + send_ui_raw($msg); # this helps when we got disconnected not to lose messages like stats + send_ui('quiz_err_server'); + } +} + +sub shuffle_text { + my ($old, $new, $length) = (shift, '', 0); + my @old = split(//, $old); + my @mov; + my $smart = ($quiz{type} == QT_SCR) ? settings_get_bool('quiz_smart_mix') : 0; + if ($smart) { + my $chars = settings_get_str('quiz_smart_mix_chars'); #? quotemeta? + for (my $i = 0; $i < @old; $i++) { + if ($old[$i] !~ /^[$chars]$/) { # hypen, apostrophe, math symbols will float + push(@mov, $i); + $length++; + } + } + $smart = 0 if ($length == length($old)); # no punctations & digits + } else { + $length = length($old); + } + return $old if ($length < 2); # skip short (and empty) + my $watchdog = ($length < $_shuffle_threshold) ? 1 : $_shuffle_watchdog; + do { + if ($smart) { + my @new = @old; + my @tmp = @mov; + my $i = 0; + while (@tmp) { + $i++ while (!grep { $_ == $i } @mov); + my $j = splice(@tmp, int(rand(@tmp)), 1); + $new[$i++] = $old[$j]; + } + $new = join('', @new); + } else { + my @tmp = @old; + $new = ''; + $new .= splice(@tmp, int(rand(@tmp)), 1) while (@tmp); + } + } until (($old ne $new) || (--$watchdog <= 0)); + return $new; +} + +sub shuffle { + my ($text, $style) = (shift, settings_get_int('quiz_words_style')); + my $keepfmt = ($quiz{type} == QT_SCR) ? 1 : settings_get_bool('quiz_words_mode'); + if ($style == 1) { + $text = lc $text; + } elsif ($style == 2) { + $text = uc $text; + } elsif ($style == 3) { + $text = join(' ', map { ucfirst lc } split(/ /, $text)); + } + if ($keepfmt) { + return join(' ', map { shuffle_text($_) } split(/ /, $text)); + } else { + $text =~ s/ //g; + return shuffle_text($text); + } +} + +sub antigoogle { + my $text = shift; + return $text unless (settings_get_bool('quiz_antigoogler') && ($text =~ / /)); + return $text if ($_protect_urls && ($text =~ m<https?://|www\.>)); + my ($fg, $bg) = (get_format('quiz_inc_question', '') =~ /^\003(\d{1,2}),(\d{1,2})/); + return $text unless (defined($fg) && defined($bg)); + ($fg, $bg) = map { int } ($fg, $bg); + my @set = ('a'..'z', 'A'..'Z', 0..9); + my @h; my @v; + #t = \00300,01 (quiz_inc_question) + #h = \0031,01 \00301,01 \00301 + #v = \0030,01 \00300,01 \00300 + if ($bg < 10) { + push(@h, "\0030$bg"); + push(@h, "\003$bg,0$bg", "\0030$bg,0$bg") if ($_randomized_antigoogler); + } else { + push(@h, "\003$bg"); + push(@h, "\003$bg,$bg") if ($_randomized_antigoogler); + } + $bg = substr("0$bg", -2); # make sure $bg is 2-char + if ($fg < 10) { + push(@v, "\0030$fg"); + push(@v, "\003$fg,$bg", "\0030$fg,$bg") if ($_randomized_antigoogler); + } else { + push(@v, "\003$fg"); + push(@v, "\003$fg,$bg") if ($_randomized_antigoogler); + } + my @lines; + if (settings_get_bool('quiz_split_long_lines')) { + # very ugly, but required calculations depending on type of question + my $raw_crap = length(get_format('quiz_inc_question', '')); + my $msg_crap = $raw_crap; + if (!$quiz{inq}) { + my $suffix = ($quiz{type} == QT_MIX) ? '_x' : ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_fm' : ''); + my $answers = keys %{$quiz{lookup}}; + my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 + $msg_crap += length(get_format('quiz_msg_question' . $suffix, [$quiz{qnum}, $quiz{qcnt}, '', $answers, answers_str($answers), $duration])); + } else { + $msg_crap += length(get_format('quiz_msg_remind', '')); + } + my $cutoff = 497 - length($quiz{chan}{server}{nick} . $quiz{chan}{server}{userhost} . $quiz{chan}{name}); + my @words = split(/ /, $text); + $text = shift(@words); + my ($line, $subst) = (1, 1); + while (@words) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $ag = ' ' if ($_smarter_antigoogler && ($subst % ($_smarter_antigoogler_chunk + 1) == 0)); + my $word = shift(@words); + if (length($text . $ag . $word) > $cutoff - (($line == 1) ? $msg_crap : $raw_crap)) { + push(@lines, $text); + $text = $word; + $line++; + $subst = 1; + } else { + $text .= $ag . $word; + $subst++; + } + } + } else { + if ($_smarter_antigoogler) { + my @words = split(/ /, $text); + $text = shift(@words); + my $subst = 1; + while (@words) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $ag = ' ' if ($subst++ % ($_smarter_antigoogler_chunk + 1) == 0); + $text .= $ag . shift(@words); + } + } else { + while ($text =~ / /) { + my $ag = $h[int(rand(@h))] . $set[int(rand(@set))] . $v[int(rand(@v))]; + $text =~ s/ /$ag/; # one by one, not /g + } + } + } + push(@lines, $text); + return @lines; +} + +sub put_dots { + my ($hint, $format, $setting, $default, $marker) = @_; + my $char = settings_get_str($setting); #? substr(settings_get_str($setting), 0, 1) + my $dot = get_format($format, ($char eq '') ? $default : $char); + $hint =~ s/$marker/$dot/g; # le trick grande finale + my ($scol, $ecol) = ($dot =~ /^(\003\d{1,2}(?:,\d{1,2})?).(\003\d{1,2}(?:,\d{1,2})?)$/); + if (defined($scol) && defined($ecol)) { + $hint =~ s/$ecol $scol/ /g; # optimize color codes + $hint =~ s/$ecol$scol//g; + $hint =~ s/$ecol$//; + } + return $hint; +} + +sub make_hint { + my $dots_only = shift; + my @words = split(/ /, $quiz{data}[$quiz{qnum}]{answer}); + if (!@{$quiz{dots}}) { # make first dots + @quiz{qw/dcnt dmax lmax/} = (0) x 3; + my ($w, $dmax) = (0) x 2; + foreach my $word (@words) { + $quiz{lmax} = length($word) if (length($word) > $quiz{lmax}); + my ($l, $hword, $dcnt) = (0, '', 0); + foreach my $letter (split(//, $word)) { + if ($letter =~ /^[a-z0-9]$/i) { + push(@{$quiz{dots}[$w]}, $l); + $hword .= ($letter =~ /^[0-9]$/) ? "\002" : "\001"; # le trick (any ASCII non-printable char) + $quiz{dcnt}++; + $dcnt++; + } else { + $hword .= $letter; + } + $l++; + } + push(@{$quiz{hwords}}, $hword); + $dmax = $dcnt if ($dcnt > $dmax); + $w++; + } + $quiz{dmax} = $dmax; + } + return '' if ($dots_only); # prep dots only + $quiz{hnum}++; + my $first_dots = settings_get_bool('quiz_first_hint_dots'); + if (!$first_dots || ($quiz{hnum} > 1)) { # reveal some dots + my $random_hints = settings_get_bool('quiz_random_hints'); + my $random_but_first = settings_get_bool('quiz_nonrandom_first_hint') && ($quiz{hnum} == ($first_dots ? 2 : 1)); + my ($w, $dmax) = (0) x 2; + foreach my $r_wdots (@{$quiz{dots}}) { + if ((ref $r_wdots) && (@$r_wdots > 0)) { + my @letters = split(//, $words[$w]); + my @hletters = split(//, $quiz{hwords}[$w]); + my $sel = (!$random_hints || $random_but_first) ? 0 : int(rand(@$r_wdots)); + $hletters[@$r_wdots[$sel]] = $letters[@$r_wdots[$sel]]; + $quiz{hwords}[$w] = join('', @hletters); + splice(@$r_wdots, $sel, 1); + $quiz{dcnt}--; + $dmax = @$r_wdots if (@$r_wdots > $dmax); + } + $w++; + } + $quiz{dmax} = $dmax; + } + my $hint = join(' ', @{$quiz{hwords}}); + $hint = put_dots($hint, 'quiz_inc_hint_alpha', 'quiz_hint_alpha', $_hint_alpha, "\001"); + $hint = put_dots($hint, 'quiz_inc_hint_digit', 'quiz_hint_digit', $_hint_digit, "\002"); + return $hint; +} + +sub make_remind { + if (!$quiz{inq} || settings_get_bool('quiz_mix_on_remind')) { + if ($quiz{type} == QT_SCR) { + $quiz{data}[$quiz{qnum}]{question} = shuffle($quiz{data}[$quiz{qnum}]{realquestion}); + } elsif ($quiz{type} == QT_MIX) { + $quiz{data}[$quiz{qnum}]{question} = shuffle($quiz{data}[$quiz{qnum}]{answer}); + } + } + return antigoogle($quiz{data}[$quiz{qnum}]{question}); +} + +sub time_str { + my ($s, $mode) = @_; + my ($h, $m) = (0) x 2; + if ($mode == T_HMS) { + $h = int($s / 3600); + $m = int($s / 60) % 60; + $s %= 60; + } + my $str = ''; + $str .= get_format('quiz_inc_hours', $h) . ' ' if ($h); + $str .= get_format('quiz_inc_minutes', $m) . ' ' if ($m); + $str .= get_format('quiz_inc_seconds' . (($mode == T_MS) ? '_ms' : ''), $s) if ($s || (!$h && !$m)); + $str =~ s/ $//; + return $str; +} + +sub flex { + my ($value, $format, $flex) = (abs(shift), shift, 0); + my @flex = split(/\//, get_format($format)); + if ($value != 1) { + $flex++; + $flex++ if ($value =~ /^[2-4]$|[^1][2-4]$/); + } + return defined($flex[$flex]) ? $flex[$flex] : '???'; # just a precaution +} + +sub score_str { return flex(shift, 'quiz_flx_points'); } # X points +sub answers_str { return flex(shift, 'quiz_flx_answers'); } # X answers +sub aquestions_str { return flex(shift, 'quiz_flx_aquestions'); } # after X questions <- AFTER! +sub fquestions_str { return flex(shift, 'quiz_flx_fquestions'); } # from X questions <- FROM! + +sub percents { + my ($val, $of) = @_; + return ($of == 0) ? 0 : $val / $of * 100; +} + +sub stop_timer { + my $timer = shift; + if ($quiz{$timer}) { + timeout_remove($quiz{$timer}); + $quiz{$timer} = undef; + } +} + +sub stop_question { + @quiz{qw/inq hnum hprot rprot dcnt dmax lmax/} = (0) x 7; + stop_timer($_) foreach (qw/tround thint tremind twarn/); + $quiz{dots} = []; + $quiz{hwords} = []; + $quiz{lookup} = {}; +} + +sub stop_quiz { + stop_question(); + @quiz{qw/ison standby/} = (0) x 2; + stop_timer('tnext'); + signal_remove('message public', 'sig_pubmsg'); +} + +sub init_first_question { + my $delay = shift; + if ($delay > 0) { + $quiz{tnext} = timeout_add_once($delay, 'evt_next_question', undef); + } else { + evt_next_question(); + } +} + +sub init_next_question { + my ($msg, $instant) = @_; + if ($quiz{qnum} >= $quiz{qcnt}) { + send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_last'), $instant); + } else { + my $delay = abs(settings_get_int('quiz_delay' . ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? '_long' : ''))); # abs in case of <0 + $delay ||= ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) ? $_next_delay_long : $_next_delay); # in case of ==0 + send_irc('quiz_msg', $msg . ' ' . get_format('quiz_msg_next' . (($quiz{type} == QT_MIX) ? '_x' : ''), $delay), $instant); + $quiz{tnext} = timeout_add_once($delay * 1000, 'evt_next_question', undef); + } +} + +sub name_to_type { + my ($name, $type) = (shift, undef); + return $name if ($name =~ /^\d+$/); + my %type = (diz => 1, std => 1, sta => 1, nrm => 1, nor => 1, zwy => 1, + mie => 2, mix => 2, lit => 2, + fam => 3, dru => 3, tea => 3, + mul => 4, all => 4, wsz => 4, bez => 4, + pom => 5, scr => 5); + foreach my $key (keys %type) { + $type = $type{$key}, last if (lc($name) =~ /^$key/i); + } + return $type; +} + +sub is_valid_data { + return (($quiz{qcnt} < 1) || + ((($quiz{type} == QT_STD) || ($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL) || + ($quiz{type} == QT_SCR)) && ($quiz{qcnt} * 2 != shift))) ? 0 : 1; +} + +sub correct_answer { + my ($addr, $nick, $timestamp, $points, $answer) = @_; + @{$quiz{players}{$addr}}{qw/besttime bestspeed/} = (0) x 2 if (!exists $quiz{players}{$addr}); + @{$quiz{players}{$addr}}{qw/nick timestamp/} = ($nick, $timestamp); + my $time = $timestamp - $quiz{qtime}; + my $speed = length($answer) / $time; + $quiz{players}{$addr}{alltime} += $time; + $quiz{players}{$addr}{allspeed} += $speed; + $quiz{players}{$addr}{besttime} = $time if (($quiz{players}{$addr}{besttime} == 0) || ($quiz{players}{$addr}{besttime} > $time)); + $quiz{players}{$addr}{bestspeed} = $speed if (($quiz{players}{$addr}{bestspeed} == 0) || ($quiz{players}{$addr}{bestspeed} < $speed)); + $quiz{players}{$addr}{score} += $points; + $quiz{players}{$addr}{answers}++; + $quiz{score} += $points; + $quiz{answers}++; + if ($quiz{type} == QT_FAM) { + $quiz{players}{$addr}{team} = 0 if (!exists $quiz{players}{$addr}{team}); # team_play is on and player is an outsider + my $team = $quiz{players}{$addr}{team}; + $quiz{teams}[$team]{score} += $points; + $quiz{teams}[$team]{answers}++; + } +} + +sub hcmd { + return sprintf(' %-37s - ', shift); +} + +sub hvar { + my ($setting, $type) = @_; + if ($type == V_INT) { + return sprintf(' %-26s : %-3d - ', $setting, settings_get_int($setting)); + } elsif ($type == V_BOOL) { + return sprintf(' %-26s : %-3s - ', $setting, settings_get_bool($setting) ? 'on' : 'off'); + } elsif ($type == V_STR) { + return sprintf(' %-26s : %-3s - ', $setting, settings_get_str($setting)); + } +} + +sub show_help { + send_ui_raw("%_$IRSSI{name}%_ v$VERSION by wilk (quiz is currently: " . ($quiz{ison} ? ($quiz{standby} ? 'on standby' : 'running') : 'off') . ')'); + send_ui_raw('%_Available commands:%_'); + send_ui_raw(hcmd("/qtype [1-$_quiz_types/name]") . 'change quiz type (see quiz_type)'); + send_ui_raw(hcmd("/qteams <2-$_max_teams>") . 'change number of teams (Familiada only)'); + send_ui_raw(hcmd("/qon [channel] <file> [1-$_quiz_types/name] [0-$_max_teams]") . 'start the quiz; you can give a type'); + send_ui_raw(hcmd('/qstats [places]') . 'display scoreboard (Familiada: 0 - teams only)'); + send_ui_raw(hcmd('/qhint') . 'show next hint (not for Familiada/Multi)'); + send_ui_raw(hcmd('/qremind') . 'remind a question'); + send_ui_raw(hcmd('/qskip') . 'skip current question'); + send_ui_raw(hcmd('/qoff') . 'stop the quiz'); + send_ui_raw(hcmd('/qdelay <seconds>') . 'change delay between questions'); + send_ui_raw(hcmd('/qtime <seconds>') . 'change round duration (Familiada/Multi only)'); + send_ui_raw(hcmd('/qreload') . 'reload questions from file'); + send_ui_raw(hcmd('/qinit') . 'reset settings to defaults'); + send_ui_raw('%_Available settings (/set):%_'); + send_ui_raw(hvar('quiz_type', V_INT) . 'quiz type (1: Dizzy, 2: Mieszacz/Literaki, 3: Familiada, 4: Multi (Familiada w/o teams), 5: Pomieszany)'); + send_ui_raw(hvar('quiz_teams', V_INT) . "number of teams (2-$_max_teams; Familiada only)"); + send_ui_raw(hvar('quiz_delay', V_INT) . 'delay between questions (sec)'); + send_ui_raw(hvar('quiz_delay_long', V_INT) . 'delay between questions (sec; Familiada/Multi only)'); + send_ui_raw(hvar('quiz_round_duration', V_INT) . 'round duration (sec; Familiada/Multi only)'); + send_ui_raw(hvar('quiz_max_hints', V_INT) . 'max. number of hints (0: no limit, >0: number of hints, <0: number of hidden chars; not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_words_style', V_INT) . 'word\'s style (0: no change, 1: l-case, 2: U-case, 3: Caps; Mieszacz/Pomieszany only)'); + send_ui_raw(hvar('quiz_anticheat_delay', V_INT) . '!hint/!remind protection delay (sec; 0: off)'); + send_ui_raw(hvar('quiz_first_anticheat_delay', V_INT) . 'first !hint/!remind protection delay (sec; 0: off)'); + send_ui_raw(hvar('quiz_points_per_answer', V_INT) . 'points per answer'); + send_ui_raw(hvar('quiz_min_points', V_INT) . 'min. points (Familiada/Multi only)'); + send_ui_raw(hvar('quiz_max_points', V_INT) . 'max. points (Familiada/Multi only)'); + send_ui_raw(hvar('quiz_scoring_mode', V_INT) . 'scoring mode (1: ppa, 2: ppa++, 3: ppa++:max, 4: min++ppa, 5: min++ppa:max, 6: max--ppa:min, 7: max->min; Familiada/Multi only)'); + send_ui_raw(hvar('quiz_ranking_type', V_INT) . 'ranking type (1: ordinal "1234", 2: dense "1223", 3: competition "1224")'); + send_ui_raw(hvar('quiz_antigoogler', V_BOOL) . 'mask questions with antigoogler?'); + send_ui_raw(hvar('quiz_split_long_lines', V_BOOL) . 'split long lines?'); + send_ui_raw(hvar('quiz_show_first_hint', V_BOOL) . 'show questions along with first hint? (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_first_hint_dots', V_BOOL) . 'first hint as dots only? (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_random_hints', V_BOOL) . 'reveal random chars in hints? otherwise left to right (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_nonrandom_first_hint', V_BOOL) . 'reveal random chars in hints, except first hint? (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_hint_alpha', V_STR) . 'character substitution in place of letters (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_hint_digit', V_STR) . 'character substitution in place of digits (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_words_mode', V_BOOL) . 'scramble with word separation? otherwise all together (Mieszacz only)'); + send_ui_raw(hvar('quiz_smart_mix', V_BOOL) . 'anchor some characters? (Pomieszany only)'); + send_ui_raw(hvar('quiz_smart_mix_chars', V_STR) . 'anchor these characters (regex; Pomieszany only)'); + send_ui_raw(hvar('quiz_mix_on_remind', V_BOOL) . 'scramble letters with each !remind? (Mieszacz/Pomieszany only)'); + send_ui_raw(hvar('quiz_strict_match', V_BOOL) . 'allow only strict answers? or allow *matching* (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_join_anytime', V_BOOL) . 'allow joining teams at any time? (Familiada only)' ); + send_ui_raw(hvar('quiz_team_play', V_BOOL) . 'allow only team players to answer? (Familiada only)'); + send_ui_raw(hvar('quiz_transfer_points', V_BOOL) . 'transfer scores when player changes teams? (Familiada only)'); + send_ui_raw(hvar('quiz_limiter', V_BOOL) . 'limit best person scores to 50%+1 points? (not for Familiada/Multi)'); + send_ui_raw(hvar('quiz_keep_scores', V_BOOL) . 'keep scores between quizes?'); + send_ui_raw(hvar('quiz_cmd_hint', V_BOOL) . '!hint command is enabled?'); + send_ui_raw(hvar('quiz_cmd_remind', V_BOOL) . '!remind command is enabled?'); +} + +##### Commands' handlers ##### +sub cmd_start { + if ($quiz{standby}) { + $quiz{standby} = 0; + init_first_question($_standby_delay); + return; + } + send_ui('quiz_err_ison'), return if ($quiz{ison}); + my ($args, $r_server, $window) = @_; + send_ui('quiz_err_server'), return if (!$r_server || !$r_server->{connected}); + my ($chan, $file, $type, $teams) = split(/ /, $args); + ($file, $chan) = ($chan, active_win()->{active}->{name}) if (!defined $file); # single arg call + send_ui('quiz_err_channel'), return if (!$chan || !$r_server->ischannel($chan)); + { + { package Irssi::Nick; } # should prevent irssi bug: "Can't locate package Irssi::Nick for @Irssi::Irc::Nick::ISA at ..." + $quiz{chan} = $r_server->channel_find($chan); + } + send_ui('quiz_err_nochannel', $chan), return if (!$quiz{chan}); + $file = (glob $file)[0]; # open() does not support "~" + send_ui('quiz_err_filename'), return if (!$file); + send_ui('quiz_err_nofile', $file), return if (!-e $file); + $type = defined($type) ? name_to_type($type) : settings_get_int('quiz_type'); + send_ui('quiz_err_type'), return if (!$type || ($type < 0) || ($type > $_quiz_types)); + if (defined $teams) { + send_ui('quiz_err_type'), return if (($type != QT_FAM) && ($type != QT_MUL)); + if (($type == QT_MUL) && ($teams >= 2)) { + $type = QT_FAM; + } elsif (($type == QT_FAM) && ($teams < 2)) { + $type = QT_MUL; + } + } else { + $teams = settings_get_int('quiz_teams'); + } + send_ui('quiz_err_teams'), return if (($type == QT_FAM) && (($teams !~ /^\d+$/) || ($teams < 2) || ($teams > $_max_teams))); + settings_set_int('quiz_type', $type); + settings_set_int('quiz_teams', $teams) if ($teams >= 2); + @quiz{qw/type tcnt file/} = ($type, $teams, $file); + my $lines = load_quiz($file); + send_ui('quiz_err_file', $file), return if (!is_valid_data($lines)); + if (!settings_get_bool('quiz_keep_scores')) { + $quiz{players} = {}; + $quiz{teams} = []; + @quiz{qw/score answers/} = (0) x 2; + } else { + #delete $quiz{players}{$_}{team} for (keys %{$quiz{players}}); #? unsure... + } + send_irc('quiz_msg_start1', INSTANT); + send_irc('quiz_msg_start2' . (($type == QT_FAM) ? '_f' : (($type == QT_MUL) ? '_m' : '')), $teams, INSTANT); + @quiz{qw/stime qnum ison/} = (time(), 0, 1); + if ($type == QT_FAM) { + $quiz{standby} = 1; + @{$quiz{teams}[$_]}{qw/score answers/} = (0) x 2 for (0 .. $teams); + } else { + $quiz{standby} = 0; + init_first_question($_start_delay); + } + signal_add_last('message public', 'sig_pubmsg'); +} + +sub cmd_stats { + send_ui('quiz_err_isoff'), return if (($quiz{score} == 0) && !$quiz{ison}); + send_ui('quiz_err_nochannel'), return if (!$quiz{chan}); + my $num = shift; + send_ui('quiz_err_ranking'), return if (($num ne '') && ($num !~ /^\d+$/)); + $num = -1 if ($num eq ''); + send_irc('quiz_msg_noscores'), return if (!keys %{$quiz{players}}); + my $qnum = $quiz{qnum}; + $qnum-- if ($quiz{inq}); + send_irc('quiz_msg_scores', [ + time_str(time() - $quiz{stime}, T_HMS), + $qnum, aquestions_str($qnum), + $quiz{qcnt}, fquestions_str($quiz{qcnt})]) if (!$quiz{standby}); + my $suffix = ''; + $suffix = '_full' if ((settings_get_int('quiz_points_per_answer') != 1) || + ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) && (settings_get_int('quiz_scoring_mode') != 1))); + if ($quiz{type} == QT_FAM) { + my @teams; + push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); + foreach my $team (1 .. $quiz{tcnt}) { + my ($score, $answers) = @{$quiz{teams}[$team]}{qw/score answers/}; + send_irc('quiz_msg_team_score' . $suffix, [ + $team, + (!defined $teams[$team]) ? '' : join(', ', @{$teams[$team]}), + $score, score_str($score), percents($score, $quiz{score}), + $answers, answers_str($answers), percents($answers, $quiz{answers})]); + } + } + return if ($quiz{standby} || (($num == 0) && ($quiz{type} == QT_FAM))); + my ($rank, $place, $exaequo, $prev, $ranking) = (0, 1, 0, undef, settings_get_int('quiz_ranking_type')); + $ranking = (($ranking < 1) || ($ranking > 3)) ? 1 : $ranking; + foreach my $player (sort { + $quiz{players}{$b}{score} <=> $quiz{players}{$a}{score} or + $quiz{players}{$b}{answers} <=> $quiz{players}{$a}{answers} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + my ($score, $answers) = @{$quiz{players}{$player}}{qw/score answers/}; + if (!defined($prev) || ($ranking == 1) || ($score != $prev)) { # 1234 + $rank += 1 + $exaequo; + $exaequo = 0; + $prev = $score; + } else { + if ($ranking == 3) { # 1224 + $exaequo++; + } elsif ($ranking == 2) { # 1223 + # nop + } else { # 1234 / fallback + $rank++; + } + } + last if ($_qstats_ranks && ($num > 0) && ($rank > $num)); + send_irc('quiz_msg_scores_place' . $suffix, [ + $rank, + $quiz{players}{$player}{nick}, + $score, score_str($score), percents($score, $quiz{score}), + $answers, answers_str($answers), percents($answers, $quiz{answers}), + $quiz{players}{$player}{besttime}, ($answers > 0) ? $quiz{players}{$player}{alltime} / $answers : 0, + $quiz{players}{$player}{bestspeed}, ($answers > 0) ? $quiz{players}{$player}{allspeed} / $answers : 0, + ($rank < 10) ? ' ' : '']); + last if (!$_qstats_ranks && ($place == $num)); + $place++; + } + return if ($num != -1); + $place = 1; + my @nicks; + foreach my $player (sort { + $quiz{players}{$a}{besttime} <=> $quiz{players}{$b}{besttime} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{besttime}])); + last if ($place >= $_qstats_records); + $place++; + } + send_irc('quiz_msg_scores_times', join(', ', @nicks)) if (@nicks); + $place = 1; + @nicks = (); + foreach my $player (sort { + $quiz{players}{$b}{bestspeed} <=> $quiz{players}{$a}{bestspeed} or + $quiz{players}{$a}{timestamp} <=> $quiz{players}{$b}{timestamp} + } keys %{$quiz{players}}) { + push(@nicks, get_format('quiz_inc_scores_record', [$place, $quiz{players}{$player}{nick}, $quiz{players}{$player}{bestspeed}])); + last if ($place >= $_qstats_records); + $place++; + } + send_irc('quiz_msg_scores_speeds', join(', ', @nicks)) if (@nicks); +} + +sub cmd_delay { + my $delay = shift; + send_ui('quiz_err_delay'), return if (($delay !~ /^\d+$/) || ($delay < 1)); + my $type = $quiz{ison} ? $quiz{type} : settings_get_int('quiz_type'); + settings_set_int('quiz_delay' . ((($type == QT_FAM) || ($type == QT_MUL)) ? '_long' : ''), $delay); + send_irc('quiz_msg_delay', $delay) if ($quiz{ison}); + send_ui('quiz_inf_delay', $delay); +} + +sub cmd_time { + my $duration = shift; + #? send_ui('quiz_err_na'), return if (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL)); + send_ui('quiz_err_duration'), return if (($duration !~ /^\d+$/) || ($duration < 1)); + settings_set_int('quiz_round_duration', $duration); + send_irc('quiz_msg_duration', $duration) if ($quiz{ison}); + send_ui('quiz_inf_duration', $duration); +} + +sub cmd_teams { + my $teams = shift; + #? send_ui('quiz_err_na'), return if (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL)); + send_ui('quiz_err_ison'), return if ($quiz{ison}); + send_ui('quiz_err_teams'), return if (($teams !~ /^\d+$/) || ($teams < 2) || ($teams > $_max_teams)); + settings_set_int('quiz_teams', $teams); + send_ui('quiz_inf_teams', $teams); +} + +sub cmd_type { + send_ui('quiz_err_ison'), return if ($quiz{ison}); + my $type = shift; + if ($type ne '') { + $type = name_to_type($type); + send_ui('quiz_err_type'), return if (!$type || ($type < 1) || ($type > $_quiz_types)); + } else { + $type = (settings_get_int('quiz_type') % $_quiz_types) + 1; + } + settings_set_int('quiz_type', $type); + send_ui('quiz_inf_type', ('Dizzy', 'Mieszacz/Literaki', 'Familiada', 'Multi (Familiada bez druzyn)', 'Pomieszany')[$type - 1]); +} + +sub cmd_skip { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + stop_question(); + init_next_question(get_format('quiz_msg_skipped')); +} + +sub cmd_hint { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_na'), return if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + send_irc('quiz_msg_hint', make_hint()); +} + +sub cmd_remind { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + send_ui('quiz_err_na'), return if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)); + send_ui('quiz_err_noquestion'), return if (!$quiz{inq}); + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + send_irc('quiz_msg_remind', get_format('quiz_inc_question', $text)); + } else { + send_irc('quiz_inc_question', $text); + } + } +} + +sub cmd_stop { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + stop_quiz(); + send_irc('quiz_msg_stop1'); + send_irc('quiz_msg_stop2', [$quiz{qnum}, time_str(time() - $quiz{stime}, T_HMS)]); +} + +sub cmd_init { + settings_set_int($_, $settings_int{$_}) for (keys %settings_int); + settings_set_bool($_, $settings_bool{$_}) for (keys %settings_bool); + settings_set_str($_, $settings_str{$_}) for (keys %settings_str); + send_ui('quiz_inf_reset'); +} + +sub cmd_reload { + send_ui('quiz_err_isoff'), return if (!$quiz{ison}); + my $cnt = $quiz{qcnt}; + my $lines = load_quiz($quiz{file}); + if (is_valid_data($lines)) { + send_ui(($quiz{qcnt} != $cnt) ? 'quiz_wrn_reload' : 'quiz_inf_reload'); + if ((($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) && $quiz{inq}) { + %{$quiz{lookup}} = map { lc($_) => $_ } keys %{$quiz{data}[$quiz{qnum}]{answers}}; + } + } else { + stop_quiz(); + send_irc('quiz_msg_stop1'); + send_irc('quiz_msg_stop2', [$quiz{qnum}, time_str(time() - $quiz{stime}, T_HMS)]); + send_ui('quiz_err_file', $quiz{file}); + } +} + +sub cmd_help { + show_help(); +} + +sub cmd_irssi_help { + my $cmd = shift; + if ($cmd =~ /^i?quiz$/) { + show_help(); + signal_stop(); + } +} + +##### Timers' events ##### +sub evt_delayed_show_msg { + my ($msg) = @_; + signal_emit('message own_public', $quiz{chan}{server}, $msg, $quiz{chan}{name}); +} + +sub evt_delayed_show_notc { + my $ref = shift; + my ($msg, $nick) = @{$ref}; + signal_emit('message irc own_notice', $quiz{chan}{server}, $msg, $nick); +} + +sub evt_delayed_load_info { + send_ui('quiz_inf_start'); +} + +sub evt_next_question { + $quiz{qtime} = time(); + $quiz{qnum}++; + my $suffix = ''; + if ($quiz{type} == QT_MIX) { + $suffix = '_x'; + } elsif (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + %{$quiz{lookup}} = map { lc($_) => $_ } keys %{$quiz{data}[$quiz{qnum}]{answers}}; + $suffix = '_fm'; + } + my $duration = abs(settings_get_int('quiz_round_duration')) || $_round_duration; # abs in case of <0, || in case of ==0 + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + my $answers = keys %{$quiz{lookup}}; + send_irc('quiz_msg_question' . $suffix, [ + $quiz{qnum}, $quiz{qcnt}, + get_format('quiz_inc_question', $text), + $answers, answers_str($answers), + $duration], INSTANT); + } else { + send_irc('quiz_inc_question', $text, INSTANT); #? not INSTANT? + } + } + if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + $quiz{tround} = timeout_add_once($duration * 1000, 'evt_round_timeout', undef); + if (($_round_warn_time > 0) && ($duration > $_round_warn_time * $_round_warn_coeff)) { + $quiz{twarn} = timeout_add_once(($duration - $_round_warn_time) * 1000, 'evt_round_timeout_warn', undef); + } + } else { + send_irc('quiz_msg_hint', make_hint()) if (settings_get_bool('quiz_show_first_hint')); + } + $quiz{inq} = 1; + my $delay = settings_get_int('quiz_first_anticheat_delay'); + if ($delay > 0) { + $quiz{hprot} = 1; + $quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); + if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { + $quiz{rprot} = 1; + $quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); + } + } +} + +sub evt_round_timeout_warn { + send_irc('quiz_msg_warn_timeout', $_round_warn_time); +} + +sub evt_round_timeout { + stop_question(); + init_next_question(get_format('quiz_msg_timeout')); #? INSTANT? +} + +##### User interaction - responses / handlers ##### +sub show_score { + my ($nick, $addr, $who) = @_; + if ($who && (lc($nick) ne lc($who))) { + my $found = 0; + foreach my $player (keys %{$quiz{players}}) { + if (lc($quiz{players}{$player}{nick}) eq lc($who)) { + send_irc('quiz_msg_score_other', [$quiz{players}{$player}{nick}, $quiz{players}{$player}{score}, score_str($quiz{players}{$player}{score})]); + $found++; + last; + } + } + send_irc('quiz_msg_noscore_other', $who) if (!$found); + } else { + if (exists $quiz{players}{$addr}) { + send_irc('quiz_msg_score', [$nick, $quiz{players}{$addr}{score}, score_str($quiz{players}{$addr}{score})]); + } else { + send_irc('quiz_msg_noscore', $nick); + } + } +} + +sub join_team { + my ($nick, $addr, $team) = @_; + return unless (($quiz{type} == QT_FAM) && (settings_get_bool('quiz_join_anytime') || $quiz{standby})); + return unless (($team >= 1) && ($team <= $quiz{tcnt})); + if (exists $quiz{players}{$addr}) { + if (settings_get_bool('quiz_transfer_points')) { + my ($score, $answers) = @{$quiz{players}{$addr}}{qw/score answers/}; + if (exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0)) { # not an outsider + my $from = $quiz{players}{$addr}{team}; + $quiz{teams}[$from]{score} -= $score; + $quiz{teams}[$from]{answers} -= $answers; + } + $quiz{teams}[$team]{score} += $score; + $quiz{teams}[$team]{answers} += $answers; + } + $quiz{players}{$addr}{team} = $team; + } else { + @{$quiz{players}{$addr}}{qw/nick timestamp team/} = ($nick, time(), $team); + @{$quiz{players}{$addr}}{qw/score answers besttime alltime bestspeed allspeed/} = (0) x 6; + } + my @teams; + push(@{$teams[$quiz{players}{$_}{team}]}, get_format('quiz_inc_team_nick', $quiz{players}{$_}{nick})) for (keys %{$quiz{players}}); + send_irc_whisper('quiz_msg_team_join', [$team, join(', ', @{$teams[$team]})], $nick) if (defined $teams[$team]); +} + +sub show_hint { + return unless (($quiz{type} != QT_FAM) && ($quiz{type} != QT_MUL) && settings_get_bool('quiz_cmd_hint')); + return if ($quiz{hprot}); + my $hints_limit = settings_get_int('quiz_max_hints'); + make_hint(PREPDOTS) if (!@{$quiz{dots}} && ($hints_limit < 0)); + if (($hints_limit == 0) || + (($hints_limit > 0) && ($quiz{hnum} < $hints_limit)) || + (($hints_limit < 0) && ($quiz{dmax} > abs($hints_limit)))) { + send_irc('quiz_msg_hint', make_hint()); + my $delay = settings_get_int('quiz_anticheat_delay'); + if ($delay > 0) { + $quiz{hprot} = 1; + $quiz{thint} = timeout_add_once($delay * 1000, sub { $quiz{hprot} = 0 }, undef); + } + } +} + +sub show_remind { + return unless (settings_get_bool('quiz_cmd_remind')); + if ((($quiz{type} == QT_MIX) || ($quiz{type} == QT_SCR)) && settings_get_bool('quiz_mix_on_remind')) { + return if ($quiz{rprot}); + my $delay = settings_get_int('quiz_anticheat_delay'); + if ($delay > 0) { + $quiz{rprot} = 1; + $quiz{tremind} = timeout_add_once($delay * 1000, sub { $quiz{rprot} = 0 }, undef); + } + } + my @lines = make_remind(); + my $line = 1; + foreach my $text (@lines) { + if ($line++ == 1) { + send_irc('quiz_msg_remind', get_format('quiz_inc_question', $text)); + } else { + send_irc('quiz_inc_question', $text); + } + } +} + +sub check_answer { + my ($nick, $addr, $answer) = @_; + if (($quiz{type} == QT_FAM) || ($quiz{type} == QT_MUL)) { + return unless (exists($quiz{lookup}{$answer}) && ($quiz{data}[$quiz{qnum}]{answers}{$quiz{lookup}{$answer}} > 0)); + return unless (($quiz{type} == QT_MUL) || !settings_get_bool('quiz_team_play') || (exists($quiz{players}{$addr}) && exists($quiz{players}{$addr}{team}) && ($quiz{players}{$addr}{team} != 0))); # last condition: for non team players there is no record + my ($time, $match) = (time(), $quiz{lookup}{$answer}); + my $answers = keys %{$quiz{data}[$quiz{qnum}]{answers}}; + my $id = $quiz{data}[$quiz{qnum}]{answers}{$match}; + my $value = $answers - $id + 1; + my $points = settings_get_int('quiz_points_per_answer'); # ppa + my $min = settings_get_int('quiz_min_points'); + my $max = settings_get_int('quiz_max_points'); + my $mode = settings_get_int('quiz_scoring_mode'); + if ($mode == 2) { # ppa++ + $points *= $value; + } elsif ($mode == 3) { # ppa++:max + $points *= $value; + $points = $max if ($points > $max); + } elsif ($mode == 4) { # min++ppa + ($points *= $value - 1) += $min; + } elsif ($mode == 5) { # min++ppa:max + ($points *= $value - 1) += $min; + $points = $max if ($points > $max); + } elsif ($mode == 6) { # max--ppa:min + $points = $max - $points * ($id - 1); + $points = $min if ($points < $min); + } elsif ($mode == 7) { # max->min + $points = int(($value - 1) * ($max - $min) / ($answers - 1) + $min + 0.5); + #} elsif ($mode == 8) { # max%:min + # $points = int($max * $value / $answers + 0.5); + # $points = $min if ($points < $min); + } + correct_answer($addr, $nick, $time, $points, $answer); + send_irc('quiz_msg_congrats', [ + $nick, + ($points == 1) ? get_format('quiz_inc_got_point', score_str($points)) : get_format('quiz_inc_got_points', [$points, score_str($points)]), + $match, + $time - $quiz{qtime}, + length($answer) / ($time - $quiz{qtime}), + $quiz{players}{$addr}{score}]); + $quiz{data}[$quiz{qnum}]{answers}{$match} *= -1; + if (!grep { $_ > 0 } values %{$quiz{data}[$quiz{qnum}]{answers}}) { + stop_question(); + init_next_question(get_format('quiz_msg_all_answers')); #? not INSTANT + } + } else { + return unless (($answer eq lc($quiz{data}[$quiz{qnum}]{answer})) || + (!settings_get_bool('quiz_strict_match') && (index($answer, lc $quiz{data}[$quiz{qnum}]{answer}) >= 0))); + my ($time, $points) = (time(), settings_get_int('quiz_points_per_answer')); + return unless (!settings_get_bool('quiz_limiter') || !exists($quiz{players}{$addr}) || + ($quiz{players}{$addr}{score} < int($quiz{qcnt} * 0.5 + 1) * $points)); # 50%+1 + stop_question(); + correct_answer($addr, $nick, $time, $points, $answer); + init_next_question(get_format('quiz_msg_congrats', [ + $nick, + ($points == 1) ? get_format('quiz_inc_got_point', score_str($points)) : get_format('quiz_inc_got_points', [$points, score_str($points)]), + $quiz{data}[$quiz{qnum}]{answer}, + $time - $quiz{qtime}, + length($answer) / ($time - $quiz{qtime}), + $quiz{players}{$addr}{score}]), INSTANT); + } +} + +##### Signals' handlers ##### +sub sig_pubmsg { + my ($r_server, $msg, $nick, $addr, $target) = @_; + return if (!$quiz{ison} || ($r_server->{tag} ne $quiz{chan}{server}{tag}) || (lc($target) ne lc($quiz{chan}{name}))); + for ($msg) { + tr/\t/ /; # tabs to spaces + s/ {2,}/ /g; # fix double spaces + s/^ +| +$//g; # trim leading/trailing spaces + s/\002|\003(?:\d{1,2}(?:,\d{1,2})?)?|\017|\026|\037//g; # remove formatting + # \002 - bold \003$fg(,$bg)? - color \017 - plain \026 - reverse \037 - underline + } + return if ($msg eq ''); + my $lmsg = lc $msg; + if ($lmsg =~ /^!score(?:\s+([^\s]+))?/) { + show_score($nick, $addr, $1) + } elsif ($lmsg =~ /^!join\s+(\d)$/) { + join_team($nick, $addr, $1); + } + return if (!$quiz{inq}); + if ($lmsg eq '!hint') { + show_hint(); + } elsif ($lmsg eq '!remind') { + show_remind(); + } + check_answer($nick, $addr, $lmsg); +} + +##### Bindings ##### +command_bind('help', 'cmd_irssi_help'); +command_bind('quiz', 'cmd_help'); +command_bind('qtype', 'cmd_type'); +command_bind('qteams', 'cmd_teams'); +command_bind('qon', 'cmd_start'); +command_bind('qdelay', 'cmd_delay'); +command_bind('qtime', 'cmd_time'); +command_bind('qhint', 'cmd_hint'); +command_bind('qremind', 'cmd_remind'); +command_bind('qskip', 'cmd_skip'); +command_bind('qstats', 'cmd_stats'); +command_bind('qoff', 'cmd_stop'); +command_bind('qreload', 'cmd_reload'); +command_bind('qinit', 'cmd_init'); + +##### User settings ##### +settings_add_int($IRSSI{name}, $_, $settings_int{$_}) for (keys %settings_int); +settings_add_bool($IRSSI{name}, $_, $settings_bool{$_}) for (keys %settings_bool); +settings_add_str($IRSSI{name}, $_, $settings_str{$_}) for (keys %settings_str); + +##### Initialization ##### +timeout_add_once($_display_delay, 'evt_delayed_load_info', undef); # le trick (workaround for info showing before script load message) diff --git a/scripts/listsort.pl b/scripts/listsort.pl new file mode 100644 index 0000000..81b9ab9 --- /dev/null +++ b/scripts/listsort.pl @@ -0,0 +1,60 @@ +use strict; +use warnings; +use Irssi; +use vars qw/$VERSION %IRSSI/; + +$VERSION = '0.1'; +%IRSSI = ( + authors => 'Isaac Good', + name => 'listsort', + contact => 'irssi@isaacgood.com', + decsription => 'Sort the /list output by channel size', + license => 'BSD', + url => 'https://github.com/IsaacG/irssi-scripts', + created => '2013/02/23', +); + +# Bindings. Start of channel list, end of list, list item. +Irssi::signal_add_last('event 322', \&list_event); +Irssi::signal_add_last('event 323', \&list_end); + +# Store the channel list between IRC messages +my %list; + +# Store list info in the hash. +sub list_event { + my ($server, $data, $server_name) = @_; + my ($meta, $more) = split (/ :/, $data, 2); + my ($nick, $name, $size) = split (/ /, $meta, 3); + $list{$name}{'size'} = $size; + + my $modes = ''; + $list{$name}{'desc'} = ''; + if ($more =~ /^[^[]*\[([^]]*)\][^ ]* *([^ ].*)$/) { + $modes = $1; + $list{$name}{'desc'} = $2; + } + + $modes =~ s/ +$//; + $list{$name}{'modes'} = $modes; +} + +# Print out the whole list in sorted order. +sub list_end { + for my $name (sort {$list{$a}{'size'} <=> $list{$b}{'size'}} keys %list) { + my $mode = $list{$name}{'modes'}; + $mode = " ($mode)" if ($mode); + my $msg = sprintf ( + "%d %s: %s%s", + $list{$name}{'size'}, + $name, + $list{$name}{'desc'}, + $mode + ); + + Irssi::print($msg, MSGLEVEL_CRAP); + } + # Drop the hash values; no point in holding them in memory. + %list = (); +} + diff --git a/scripts/logcompress_perl.pl b/scripts/logcompress_perl.pl index 062331e..d75755a 100644 --- a/scripts/logcompress_perl.pl +++ b/scripts/logcompress_perl.pl @@ -4,7 +4,7 @@ use Irssi; use IO::Compress::Gzip qw(gzip $GzipError); use vars qw($VERSION %IRSSI); -$VERSION = "0.01"; +$VERSION = "0.02"; %IRSSI = ( authors => 'vague', contact => 'vague!#irssi@fgreenode', @@ -12,12 +12,13 @@ $VERSION = "0.01"; description => "compress logfiles then they're rotated, modified from original logcompress.pl to use perl modules instead", license => "Public Domain", url => "http://irssi.org/", - changed => "2016-01-31T01:45+0100" + changed => "2017-01-07T12:00+0100" ); sub sig_rotate { my $input = $_[0]->{real_fname}; gzip $input => "$input.gz" or Irssi::print(MSGLEVEL_CLIENTERROR, "gzip failed: $GzipError\n"); + unlink $input if -e "$input.gz"; } Irssi::signal_add('log rotated', 'sig_rotate'); diff --git a/scripts/mh_sbuserinfo.pl b/scripts/mh_sbuserinfo.pl index fde7ebc..c9ee211 100644 --- a/scripts/mh_sbuserinfo.pl +++ b/scripts/mh_sbuserinfo.pl @@ -1,8 +1,8 @@ ############################################################################## # -# mh_sbuserinfo.pl v1.04 (20151225) +# mh_sbuserinfo.pl v1.05 (20161106) # -# Copyright (c) 2015 Michael Hansen +# Copyright (c) 2015, 2016 Michael Hansen # # Permission to use, copy, modify, and distribute this software # for any purpose with or without fee is hereby granted, provided @@ -25,23 +25,41 @@ # displays in the statusbar the number of users and the limit of the channel, # with several settings for finetuning: # -# default settings: [Users: <users>(@<users_op>:+<users_voice>:<users_rest>)/<limit>(<limitusers>)] +# default settings: [Users: <users>(*<users_oper>:@<users_op>:+<users_voice>:<users_rest>)/<limit>(<limitusers>)] # "/<limit>(<limitusers>)" will only show when there is a limit set. # "(<limitusers>)" shows the difference between the limit and current # users (this can be negative if the limit is lower than users) # +# setting mh_sbuserinfo_format_group_begin (default '(') and +# setting mh_sbuserinfo_format_group_end' (default ')'); change the characters grouping +# details +# +# setting mh_sbuserinfo_format_sep (default ':'): change the : seperator to another string +# +# setting mh_sbuserinfo_format_div (default '/'): change the / divider to another string +# # setting mh_sbuserinfo_show_prefix (default 'Users: '): set/unset the prefix # in the window item # # setting mh_sbuserinfo_show_details (default ON): enable/disable showing a -# detailed breakout of users into ops, halfops, voice and normal +# detailed breakout of users into opers, ops, halfops, voice and normal # # setting mh_sbuserinfo_show_details_mode (default ON): enable/disable -# prefixing ops, halfops and voice with @%+ when details are enabled +# prefixing opers, ops, halfops and voice with *@%+ when details are enabled +# +# setting mh_sbuserinfo_format_mode_oper (default '*'), +# setting mh_sbuserinfo_format_mode_op (default '@'), +# setting mh_sbuserinfo_format_mode_ho (default '%%'), +# setting mh_sbuserinfo_format_mode_vo (default '+') and +# setting mh_sbuserinfo_format_mode_other (default ''): change the mode prefix +# for each of oper, op, halfdop, voice and others # # setting mh_sbuserinfo_show_details_halfop (default OFF): enable/disable # showing halfops when details are enabled # +# setting mh_sbuserinfo_show_details_oper (default ON): enable/disable +# showing opers when details are enabled +# # setting mh_sbuserinfo_show_details_difference (default ON): enable/disable # showing the "(<limitusers>)" # @@ -51,9 +69,13 @@ # setting mh_sbuserinfo_show_warning_limit (default ON): change the colour # of "<limit>" if channel is above, at or close to the limited amount of users # -# setting mh_sbuserinfo_show_warning_limit_percent (default 95): number in +# setting mh_sbuserinfo_show_warning_limit_percent (default 0): number in # percent (0-100) of users relative to the limit before a limit warning is -# triggered +# triggered (if set to 0 see mh_sbuserinfo_show_warning_limit_difference) +# +# setting mh_sbuserinfo_show_warning_limit_difference (default 5): when +# mh_sbuserinfo_show_warning_limit_percent is 0, use this absolute value +# as the difference warning trigger instead of percentage # # setting mh_sbuserinfo_warning_format (default '%Y'): the colour used for # warnings. see http://www.irssi.org/documentation/formats @@ -63,6 +85,15 @@ # see '/help statusbar' for more details and do not forget to '/save' # # history: +# +# v1.05 (20161106) +# added setting _show_details_oper and supporting code +# added setting _format_sep and supportingf code +# added setting _format_div and supporting code +# added setting _group_begin and _format_group_end and supporting code +# added setting _format_mode_oper, _format_mode_op, _format_mode_ho, _format_mode_vo and _format_mode_other, and supporting code +# added settting _show_warning_limit_difference and supporting code (changing _show_warning_limit_percent behavior) +# changed default of _show_warning_limit_percent from 95 to 0 # v1.04 (20151225) # added setting _show_details_difference and supporting code # changed _show_warning_limit_percent default from 90 to 95 @@ -96,7 +127,7 @@ use strict; use Irssi 20100403; use Irssi::TextUI; -our $VERSION = '1.04'; +our $VERSION = '1.05'; our %IRSSI = ( 'name' => 'mh_sbuserinfo', @@ -105,7 +136,7 @@ our %IRSSI = 'authors' => 'Michael Hansen', 'contact' => 'mh on IRCnet #help', 'url' => 'http://scripts.irssi.org / https://github.com/mh-source/irssi-scripts', - 'changed' => 'Fri Dec 25 17:14:34 CET 2015', + 'changed' => 'Sun Nov 6 20:37:05 CET 2016', ); ############################################################################## @@ -183,6 +214,7 @@ sub statusbar_userinfo my $users_op = 0; my $users_ho = 0; my $users_vo = 0; + my $users_oper = 0; my $warning_format = Irssi::settings_get_str('mh_sbuserinfo_warning_format'); for my $nick ($channel->nicks()) @@ -201,56 +233,84 @@ sub statusbar_userinfo { $users_vo++; } + + if ($nick->{'serverop'}) + { + $users_oper++; + } } - $format = $format . $users; + $format .= $users; + + my $format_sep = Irssi::settings_get_str('mh_sbuserinfo_format_sep'); + my $format_div = Irssi::settings_get_str('mh_sbuserinfo_format_div'); + + my $format_group_begin = Irssi::settings_get_str('mh_sbuserinfo_format_group_begin'); + my $format_group_end = Irssi::settings_get_str('mh_sbuserinfo_format_group_end'); if (Irssi::settings_get_bool('mh_sbuserinfo_show_details')) { - $format = $format . '('; + $format .= $format_group_begin; my $showmode = Irssi::settings_get_bool('mh_sbuserinfo_show_details_mode'); + if (Irssi::settings_get_bool('mh_sbuserinfo_show_details_oper')) + { + if ($showmode) + { + $format .= Irssi::settings_get_str('mh_sbuserinfo_format_mode_oper'); + } + + $format .= $users_oper . $format_sep + } + if (Irssi::settings_get_bool('mh_sbuserinfo_show_warning_opless') and (not $users_op)) { - $format = $format . $warning_format; + $format .= $warning_format; } if ($showmode) { - $format = $format . '@'; + $format .= Irssi::settings_get_str('mh_sbuserinfo_format_mode_op'); } - $format = $format . $users_op . '%n:'; + $format .= $users_op . '%n' . $format_sep; if (Irssi::settings_get_bool('mh_sbuserinfo_show_details_halfop')) { # # add halfops to ops so users calculation below matches # - $users_op = $users_op + $users_ho; + $users_op += $users_ho; if ($showmode) { - $format = $format . '%%'; + $format .= Irssi::settings_get_str('mh_sbuserinfo_format_mode_ho'); } - $format = $format . $users_ho . ':'; + $format .= $users_ho . $format_sep; + } + + if ($showmode) + { + $format .= Irssi::settings_get_str('mh_sbuserinfo_format_mode_vo');; } + $format .= $users_vo . $format_sep; + if ($showmode) { - $format = $format . '+'; + $format .= Irssi::settings_get_str('mh_sbuserinfo_format_mode_other');; } - $format = $format . $users_vo . ':' . ($users - ($users_op + $users_vo)) . ')'; + $format .= ($users - ($users_op + $users_vo)) . $format_group_end; } my $limit = $channel->{'limit'}; if ($limit) { - $format = $format . '/'; + $format .= $format_div; if (Irssi::settings_get_bool('mh_sbuserinfo_show_warning_limit')) { @@ -265,20 +325,39 @@ sub statusbar_userinfo $setting_percent = 0; } - my $percent = int(($users / $limit) * 100); + if ($setting_percent) + { + + my $percent = int(($users / $limit) * 100); - if ($percent >= $setting_percent) + if ($percent >= $setting_percent) + { + $format .= $warning_format; + } + } else { - $format = $format . $warning_format; + my $setting_percent = Irssi::settings_get_int('mh_sbuserinfo_show_warning_limit_difference'); + + my $difference = ($limit - $users); + + if ($setting_percent < 0) + { + $setting_percent = 0; + } + + if ($difference < $setting_percent) + { + $format .= $warning_format; + } } } if (Irssi::settings_get_bool('mh_sbuserinfo_show_details_difference')) { - $limit = $limit . '(' . ($limit - $users) . ')'; + $limit .= $format_group_begin . ($limit - $users) . $format_group_end; } - $format = $format . $limit . '%n'; + $format .= $limit . '%n'; } } } @@ -292,17 +371,26 @@ sub statusbar_userinfo # ############################################################################## -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details', 1); -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_mode', 1); -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_halfop', 0); -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_warning_opless', 1); -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_warning_limit', 1); -Irssi::settings_add_int( 'mh_sbuserinfo', 'mh_sbuserinfo_show_warning_limit_percent', 95); -Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_warning_format', '%Y'); -Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_show_prefix', 'Users: '); -Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_difference', 1); - -Irssi::statusbar_item_register('mh_sbuserinfo', '', 'statusbar_userinfo'); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details', 1); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_mode', 1); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_halfop', 0); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_warning_opless', 1); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_warning_limit', 1); +Irssi::settings_add_int( 'mh_sbuserinfo', 'mh_sbuserinfo_show_warning_limit_percent', 0); +Irssi::settings_add_int( 'mh_sbuserinfo', 'mh_sbuserinfo_show_warning_limit_difference', 5); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_warning_format', '%Y'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_show_prefix', 'Users: '); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_difference', 1); +Irssi::settings_add_bool('mh_sbuserinfo', 'mh_sbuserinfo_show_details_oper', 1); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_sep', ':'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_div', '/'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_group_begin', '('); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_group_end', ')'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_mode_oper', '*'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_mode_op', '@'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_mode_ho', '%%'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_mode_vo', '+'); +Irssi::settings_add_str( 'mh_sbuserinfo', 'mh_sbuserinfo_format_mode_other', ''); Irssi::signal_add_last('channel sync', 'statusbar_redraw'); Irssi::signal_add_last('channel mode changed', 'statusbar_redraw'); @@ -312,6 +400,8 @@ Irssi::signal_add_last('nicklist remove', 'statusbar_redraw'); Irssi::signal_add_last('setup changed', 'signal_setup_changed_last'); Irssi::signal_add_last('window changed', 'signal_window_changed_last'); +Irssi::statusbar_item_register('mh_sbuserinfo', '', 'statusbar_userinfo'); + 1; ############################################################################## diff --git a/scripts/nickserv.pl b/scripts/nickserv.pl index c1d9ce8..ff2738d 100644 --- a/scripts/nickserv.pl +++ b/scripts/nickserv.pl @@ -24,7 +24,7 @@ use strict; use Irssi; use vars qw($VERSION %IRSSI); -$VERSION = "1.10"; +$VERSION = "1.11"; %IRSSI = ( authors => 'Geert Hauwaerts', @@ -32,37 +32,47 @@ $VERSION = "1.10"; name => 'nickserv.pl', description => 'This script will authorize you into NickServ.', license => 'GNU General Public License', - url => 'http://irssi.hauwaerts.be/nickserv.pl', + url => 'https://github.com/irssi/scripts.irssi.org/blob/master/scripts/nickserv.pl', + changed => 'Di 17. Jan 19:32:45 CET 2017', ); +my $irssidir = Irssi::get_irssi_dir(); + my @nickservnet = (); -my $nickservnet_file = "nickserv.networks"; +my $nickservnet_file = "$irssidir/nickserv.networks"; my @nickservauth = (); -my $nickservauth_file = "nickserv.auth"; +my $nickservauth_file = "$irssidir/nickserv.auth"; -my $irssidir = Irssi::get_irssi_dir(); +my @nickservpostcmd = (); +my $nickservpostcmd_file = "$irssidir/nickserv.postcmd"; my $help = <<EOF; Usage: (all on one line) /NICKSERV [addnet <ircnet> <services\@host>] [addnick <ircnet> <nickname> <password>] + [addpostcmd <ircnet> <nickname> <command>] [delnet <ircnet>] [delnick <ircnet> <nick>] - [help listnet listnick] - -addnet: Add a new network into the NickServ list. -addnick: Add a new nickname into the NickServ list. -delnet: Delete a network from the NickServ list. -delnick: Delete a nickname from the NickServ list. -listnet: Display the contents of the NickServ network list. -listnick: Display the contents of the NickServ nickname list. -help: Display this useful little helptext. + [delpostcmd <ircnet> <nick>] + [help listnet listnick listpostcmd] + +addnet: Add a new network into the NickServ list. +addnick: Add a new nickname into the NickServ list. +addpostcmd: Add a new post auth command for nickname into the NickServ list. +delnet: Delete a network from the NickServ list. +delnick: Delete a nickname from the NickServ list. +delpostcmd: Deletes all post auth commands for the given nickame. +listnet: Display the contents of the NickServ network list. +listnick: Display the contents of the NickServ nickname list. +listpostcmd: Display the contents of the NickServ postcmd list. +help: Display this useful little helptext. Examples: (all on one line) /NICKSERV addnet Freenode NickServ\@services. /NICKSERV addnick Freenode Geert mypass +/NICKSERV addpostcmd Freenode Geert ^MSG ChanServ invite #heaven /NICKSERV delnet Freenode /NICKSERV delnick Freenode Geert @@ -74,19 +84,25 @@ EOF Irssi::theme_register([ 'nickserv_usage_network', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV addnet ircnet services@host%_".', 'nickserv_usage_nickname', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV addnick ircnet nickname password%_".', + 'nickserv_usage_postcmd', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV addpostcmd ircnet nickname command%_".', 'nickserv_delusage', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV delnet ircnet%_".', 'nickserv_delnickusage', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV delnick ircnet nickname%_".', - 'nickserv_delled', '%R>>%n %_NickServ:%_ Deleted %_$0%_ and his nicknames from the NickServ ircnet list.', - 'nickserv_delled_nick', '%R>>%n %_NickServ:%_ Deleted %_$1%_ from the NickServ list on $0.', + 'nickserv_delpostcmdusage', '%R>>%n %_NickServ:%_ Insufficient parameters: Usage "%_/NICKSERV delpostcmd ircnet nickname%_".', + 'nickserv_delled', '%R>>%n %_NickServ:%_ Deleted %_$0%_ and it\'s nicknames and post commands from the NickServ ircnet list.', + 'nickserv_delled_nick', '%R>>%n %_NickServ:%_ Deleted %_$1%_ and it\'s post commands from the NickServ list on $0.', + 'nickserv_delled_postcmd', '%R>>%n %_NickServ:%_ Deleted all entries for %_$1%_ from the NickServ postcmd list on $0.', 'nickserv_nfound', '%R>>%n %_NickServ:%_ The NickServ ircnet %_$0%_ could not be found.', 'nickserv_nfound_nick', '%R>>%n %_NickServ:%_ The NickServ nickname %_$0%_ could not be found on $1.', + 'nickserv_nfound_postcmd', '%R>>%n %_NickServ:%_ The NickServ post commands for nickname %_$1%_ could not be found on $0.', 'nickserv_usage', '%R>>%n %_NickServ:%_ Insufficient parameters: Use "%_/NICKSERV help%_" for further instructions.', 'nickserv_no_net', '%R>>%n %_NickServ:%_ Unknown Irssi ircnet %_$0%_.', 'nickserv_wrong_host', '%R>>%n %_NickServ:%_ Malformed services hostname %_$0%_.', 'already_loaded_network', '%R>>%n %_NickServ:%_ The ircnet %_$0%_ already exists in the NickServ ircnet list, please remove it first.', 'nickserv_loaded_nick', '%R>>%n %_NickServ:%_ The nickname %_$0%_ already exists in the NickServ authlist on %_$1%_, please remove it first.', 'nickserv_not_loaded_net', '%R>>%n %_NickServ:%_ The ircnet %_$0%_ doesn\'t exists in the NickServ ircnet list, please add it first.', + 'nickserv_not_loaded_nick', '%R>>%n %_NickServ:%_ The nickname %_$0%_ doesn\'t exists in the NickServ authlist on %_$1%_, please add it first.', 'saved_nickname', '%R>>%n %_NickServ:%_ Added nickname %_$1%_ on %_$0%_.', + 'saved_postcmd', '%R>>%n %_NickServ:%_ Added postcmd %_$1%_ on %_$0%_: %_%2%_.', 'network_print', '$[!-2]0 $[20]1 $2', 'password_request', '%R>>%n %_NickServ:%_ Auth Request from NickServ on %_$0%_.', 'password_accepted', '%R>>%n %_NickServ:%_ Password accepted on %_$0%_.', @@ -96,6 +112,9 @@ Irssi::theme_register([ 'nickname_print', '$[!-2]0 $[20]1 $[18]2 $3', 'nickname_info', '%_ # Ircnet Nickname Password%_', 'nickname_empty', '%R>>%n %_NickServ:%_ Your NickServ authlist is empty.', + 'postcmd_print', '$[!-2]0 $[20]1 $[18]2 $3', + 'postcmd_info', '%_ # Ircnet Nickname Postcmd%_', + 'postcmd_empty', '%R>>%n %_NickServ:%_ Your NickServ postcmd list is empty.', 'nickserv_help', '$0', 'saved_network', '%R>>%n %_NickServ:%_ Added services mask "%_$1%_" on %_$0%_.', 'nickserv_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.' @@ -105,139 +124,171 @@ sub load_nickservnet { my ($file) = @_; - @nickservnet = (); + @nickservnet = load_file($file, sub { + my $new_nsnet = new_nickserv_network(@_); + return undef if ($new_nsnet->{name} eq "" || $new_nsnet->{host} eq ""); + return $new_nsnet; + }); +} - if (-e $file) { - local *F; - open(F, "<", $file); - local $/ = "\n"; +sub save_nickservnet { - while (<F>) { - chop; - my $new_nsnet = new_nickserv_network(split("\t")); - - if (($new_nsnet->{name} ne "") && ($new_nsnet->{host} ne "")) { - push(@nickservnet, $new_nsnet); - } - } - - close(F); - } + save_file($nickservnet_file, \@nickservnet, \&nickservnet_as_list); } -sub save_nickservnet { +sub new_nickserv_network { - my ($file) = @_; + return { + name => shift, + host => shift + }; +} - return unless scalar @nickservnet; # there's nothing to save +sub nickservnet_as_list { - if (-e $file) { - local *F; - open(F, ">", $file); + my $nickserv_net = shift; - for (my $n = 0; $n < @nickservnet; ++$n) { - print(F join("\t", $nickservnet[$n]->{name}, $nickservnet[$n]->{host}) . "\n"); - } - - close(F); - } else { - create_network_file($file); - save_nickservnet($file); - } + return ( + $nickserv_net->{name}, + $nickserv_net->{host} + ); } -sub create_network_file { - +sub load_nickservnick { + my ($file) = @_; - - open(F, ">", $file) or die "Can't create $file. Reason: $!"; + + @nickservauth = load_file($file, sub { + my $new_nsnick = new_nickserv_nick(@_); + + return undef if ($new_nsnick->{ircnet} eq "" || $new_nsnick->{nick} eq "" || $new_nsnick->{pass} eq ""); + return $new_nsnick; + }); } -sub new_nickserv_network { +sub save_nickservnick { - my $nsnet = {}; + save_file($nickservauth_file, \@nickservauth, \&nickserv_nick_as_list); +} - $nsnet->{name} = shift; - $nsnet->{host} = shift; +sub new_nickserv_nick { - return $nsnet; + return { + ircnet => shift, + nick => shift, + pass => shift + }; } -sub load_nickservnick { +sub nickserv_nick_as_list { + + my $nickserv_nick = shift; + return ( + $nickserv_nick->{ircnet}, + $nickserv_nick->{nick}, + $nickserv_nick->{pass} + ); +} + +sub load_nickservpostcmd { my ($file) = @_; - @nickservauth = (); + @nickservpostcmd = load_file($file, sub { + my $new_postcmd = new_postcmd(@_); - if (-e $file) { - local *F; - open(F, "<" ,$file); - local $/ = "\n"; + return undef if ($new_postcmd->{ircnet} eq "" || $new_postcmd->{nick} eq "" || $new_postcmd->{postcmd} eq ""); + return $new_postcmd; + }); +} - while (<F>) { - chop; - my $new_nsnick = new_nickserv_nick(split("\t")); - - if (($new_nsnick->{ircnet} ne "") && ($new_nsnick->{nick} ne "") && ($new_nsnick->{pass} ne "")) { - push(@nickservauth, $new_nsnick); - } - } - - close(F); - } +sub save_nickservpostcmd { + + save_file($nickservpostcmd_file, \@nickservpostcmd, \&postcmd_as_list); } -sub save_nickservnick { +sub new_postcmd { - my ($file) = @_; + return { + ircnet => shift, + nick => shift, + postcmd => shift + }; +} - return unless scalar @nickservauth; # there's nothing to save +sub postcmd_as_list { + my $postcmd = shift; - if (-e $file) { - local *F; - open(F, ">", $file); + return ( + $postcmd->{ircnet}, + $postcmd->{nick}, + $postcmd->{postcmd} + ); +} - for (my $n = 0; $n < @nickservauth; ++$n) { - print(F join("\t", $nickservauth[$n]->{ircnet}, $nickservauth[$n]->{nick}, $nickservauth[$n]->{pass}) . "\n"); - } - - close(F); - } else { - create_nick_file($file); - save_nickservnick($file); +# file: filename to be read +# parse_line_fn: receives array of entries of a single line as input, should +# return parsed data object or undef in the data is incomplete +# returns: parsed data array +sub load_file { + + my ($file, $parse_line_fn) = @_; + my @parsed_data = (); + + if (-e $file) { + open(my $fh, "<", $file); + local $/ = "\n"; + + while (<$fh>) { + chomp; + my $data = $parse_line_fn->(split("\t")); + push(@parsed_data, $data) if $data; } -} -sub create_nick_file { - - my ($file) = @_; - - my $umask = umask 0077; # save old umask - open(F, ">", $file) or die "Can't create $file. Reason: $!"; - umask $umask; + close($fh); + } + + return @parsed_data; } -sub new_nickserv_nick { +# file: filename to be written, is created accessable only by the user +# data_ref: array ref of data entries +# serialize_fn: receives a data reference and should return an array or tuples +# for that data that will be serialized into one line +sub save_file { + + my ($file, $data_ref, $serialize_fn) = @_; + + create_private_file($file) unless -e $file; - my $nsnick = {}; + open(my $fh, ">", $file) or die "Can't create $file. Reason: $!"; - $nsnick->{ircnet} = shift; - $nsnick->{nick} = shift; - $nsnick->{pass} = shift; + for my $data (@$data_ref) { + print($fh join("\t", $serialize_fn->($data)), "\n"); + } + + close($fh); +} - return $nsnick; +sub create_private_file { + + my ($file) = @_; + my $umask = umask 0077; # save old umask + open(my $fh, ">", $file) or die "Can't create $file. Reason: $!"; + close($fh); + umask $umask; } sub add_nickname { - + my ($network, $nickname, $password) = split(" ", $_[0], 3); - my ($correct_network, $correct_nickname, $correct_password); + my ($correct_network, $correct_nickname); if ($network eq "" || $nickname eq "" || $password eq "") { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_usage_nickname'); return; } - + if ($network) { if (!already_loaded_net($network)) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_not_loaded_net', $network); @@ -246,7 +297,7 @@ sub add_nickname { $correct_network = 1; } } - + if ($nickname) { if (already_loaded_nick($nickname, $network)) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_loaded_nick', $nickname, $network); @@ -255,28 +306,64 @@ sub add_nickname { $correct_nickname = 1; } } - + if ($correct_network && $correct_nickname) { push(@nickservauth, new_nickserv_nick($network, $nickname, $password)); - save_nickservnick("$irssidir/$nickservauth_file"); - + save_nickservnick(); + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'saved_nickname', $network, $nickname); } } +sub add_postcmd { + + my ($network, $nickname, $postcmd) = split(" ", $_[0], 3); + my ($correct_network, $correct_nickname); + + if ($network eq "" || $nickname eq "" || $postcmd eq "") { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_usage_postcmd'); + return; + } + + if ($network) { + if (!already_loaded_net($network)) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_not_loaded_net', $network); + return; + } else { + $correct_network = 1; + } + } + + if ($nickname) { + if (!already_loaded_nick($nickname, $network)) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_not_loaded_nick', $nickname, $network); + return; + } else { + $correct_nickname = 1; + } + } + + if ($correct_network && $correct_nickname) { + push(@nickservpostcmd, new_postcmd($network, $nickname, $postcmd)); + save_nickservpostcmd(); + + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'saved_postcmd', $network, $nickname, $postcmd); + } +} + sub add_network { - + my ($network, $hostname) = split(" ", $_[0], 2); my ($correct_net, $correct_host); - + if ($network eq "" || $hostname eq "") { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_usage_network'); return; } - + if ($network) { my ($ircnet) = Irssi::chatnet_find($network); - + if (!$ircnet) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_no_net', $network); return; @@ -287,7 +374,7 @@ sub add_network { $correct_net = 1; } } - + if ($hostname) { if ($hostname !~ /^[.+a-zA-Z0-9_-]{1,}@[.+a-zA-Z0-9_-]{1,}$/) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_wrong_host', $hostname); @@ -296,11 +383,11 @@ sub add_network { $correct_host = 1; } } - + if ($correct_net && $correct_host) { push(@nickservnet, new_nickserv_network($network, $hostname)); - save_nickservnet("$irssidir/$nickservnet_file"); - + save_nickservnet(); + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'saved_network', $network, $hostname); } } @@ -308,56 +395,32 @@ sub add_network { sub already_loaded_net { my ($ircnet) = @_; - my $loaded = check_loaded_net($ircnet); - - if ($loaded > -1) { - return 1; - } - - return 0; -} - -sub check_loaded_net { - - my ($ircnet) = @_; $ircnet = lc($ircnet); - for (my $loaded = 0; $loaded < @nickservnet; ++$loaded) { - return $loaded if (lc($nickservnet[$loaded]->{name}) eq $ircnet); + for my $loaded (@nickservnet) { + return 1 if (lc($loaded->{name}) eq $ircnet); } - - return -1; + + return 0; } sub already_loaded_nick { - my ($nickname, $network) = @_; - my $loaded = check_loaded_nick($nickname, $network); - - if ($loaded > -1) { - return 1; - } - - return 0 -} -sub check_loaded_nick { - - my ($nickname, $network) = @_; - $nickname = lc($nickname); $network = lc($network); - - for (my $loaded = 0; $loaded < @nickservauth; ++$loaded) { - return $loaded if (lc($nickservauth[$loaded]->{nick}) eq $nickname && lc ($nickservauth[$loaded]->{ircnet}) eq $network); + + for my $loaded (@nickservauth) { + return 1 if (lc($loaded->{nick}) eq $nickname && + lc($loaded->{ircnet}) eq $network); } - - return -1; + + return 0; } sub list_net { - + if (@nickservnet == 0) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'network_empty'); } else { @@ -370,7 +433,7 @@ sub list_net { } sub list_nick { - + if (@nickservauth == 0) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickname_empty'); } else { @@ -382,8 +445,21 @@ sub list_nick { } } +sub list_postcmd { + + if (@nickservpostcmd == 0) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'postcmd_empty'); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'postcmd_info'); + + for (my $n = 0; $n < @nickservpostcmd ; ++$n) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'postcmd_print', $n, $nickservpostcmd[$n]->{ircnet}, $nickservpostcmd[$n]->{nick}, $nickservpostcmd[$n]->{postcmd}); + } + } +} + sub nickserv_notice { - + my ($server, $data, $nick, $address) = @_; my ($target, $text) = $data =~ /^(\S*)\s:(.*)/; @@ -392,14 +468,14 @@ sub nickserv_notice { if ($text =~ /^(?:If this is your nickname, type|Please identify via|Type) \/msg NickServ (?i:identify)/ || $text =~ /^This nickname is registered and protected. If it is your/ || $text =~ /This nickname is registered\. Please choose a different nickname/) { my $password = get_password($server->{tag}, $server->{nick}); - + if ($password == -1) { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'password_request', $server->{tag}); Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_nfound_nick', $server->{nick}, $server->{tag}); Irssi::signal_stop(); return; } - + Irssi::signal_stop(); Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'password_request', $server->{tag}); $server->command("^MSG NickServ IDENTIFY $password"); @@ -442,6 +518,7 @@ sub nickserv_notice { } elsif ($text =~ /^Password accepted - you are now recognized/ || $text =~ /^You are now identified for/) { Irssi::signal_stop(); Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'password_accepted', $server->{tag}); + run_postcmds($server, $server->{tag}, $server->{nick}) } elsif ($text =~ /^Password Incorrect/ || $text =~ /^Password incorrect./) { Irssi::signal_stop(); Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'password_wrong', $server->{tag}); @@ -449,24 +526,39 @@ sub nickserv_notice { } } +sub run_postcmds { + my ($server, $ircnet, $nick) = @_; + return if @nickservpostcmd == 0; + + for my $cmd (@nickservpostcmd) { + if ($ircnet eq $cmd->{ircnet} && + $nick eq $cmd->{nick} && + $cmd->{postcmd}) { + $server->command($cmd->{postcmd}); + } + } +} + sub is_nickserv { - + my ($net, $host) = @_; for (my $loaded = 0; $loaded < @nickservnet; ++$loaded) { - return 1 if (lc($nickservnet[$loaded]->{name}) eq lc($net) && lc($nickservnet[$loaded]->{host}) eq lc($host)); + return 1 if (lc($nickservnet[$loaded]->{name}) eq lc($net) && + lc($nickservnet[$loaded]->{host}) eq lc($host)); } return 0; } sub get_password { - + my ($ircnet, $nick) = @_; - + for (my $loaded = 0; $loaded < @nickservauth; ++$loaded) { - return $nickservauth[$loaded]->{pass} if (lc($nickservauth[$loaded]->{ircnet}) eq lc($ircnet) && lc($nickservauth[$loaded]->{nick}) eq lc($nick)); + return $nickservauth[$loaded]->{pass} if (lc($nickservauth[$loaded]->{ircnet}) eq lc($ircnet) && + lc($nickservauth[$loaded]->{nick}) eq lc($nick)); } - + return -1; } @@ -485,47 +577,76 @@ sub del_network { $ircnetindex = 1; } } - + if ($ircnetindex) { @nickservnet = grep {lc($_->{name}) ne lc($ircnet)} @nickservnet; @nickservauth = grep {lc($_->{ircnet}) ne lc($ircnet)} @nickservauth; + @nickservpostcmd = grep {lc($_->{ircnet}) ne lc($ircnet)} @nickservpostcmd; Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_delled', $ircnet); - save_nickservnet("$irssidir/$nickservnet_file"); - save_nickservnick("$irssidir/$nickservauth_file"); + save_nickservnet(); + save_nickservnick(); + save_nickservpostcmd(); } else { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_nfound', $ircnet); } } sub del_nickname { - + my ($ircnet, $nickname) = split(" ", $_[0], 2); my ($nickindex); - + if ($ircnet eq "" || $nickname eq "") { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_delnickusage'); return; } for (my $index = 0; $index < @nickservauth; ++$index) { - if (lc($nickservauth[$index]->{ircnet}) eq lc($ircnet) && lc($nickservauth[$index]->{nick}) eq lc($nickname)) { + if (lc($nickservauth[$index]->{ircnet}) eq lc($ircnet) && + lc($nickservauth[$index]->{nick}) eq lc($nickname)) { $nickindex = splice(@nickservauth, $index, 1); - } + } } if ($nickindex) { + @nickservpostcmd = grep {lc($_->{ircnet}) ne lc($ircnet) || + lc($_->{nick}) ne lc($nickname)} + @nickservpostcmd; + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_delled_nick', $ircnet, $nickname); - save_nickservnick("$irssidir/$nickservauth_file"); + save_nickservnick(); + save_nickservpostcmd(); } else { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_nfound_nick', $ircnet, $nickname); } } +sub del_postcmd { + + my ($ircnet, $nickname) = split(" ", $_[0], 2); + + if ($ircnet eq "" || $nickname eq "") { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_delpostcmdusage'); + return; + } + + my $size_before = scalar(@nickservpostcmd); + @nickservpostcmd = grep { !( lc($_->{ircnet}) eq lc($ircnet) && lc($_->{nick}) eq lc($nickname) )} @nickservpostcmd; + my $size_after = scalar(@nickservpostcmd); + + if ($size_before != $size_after) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_delled_postcmd', $ircnet, $nickname); + save_nickservpostcmd(); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_nfound_postcmd', $ircnet, $nickname); + } +} + sub nickserv_runsub { - + my ($data, $server, $item) = @_; $data =~ s/\s+$//g; - + if ($data) { Irssi::command_runsub('nickserv', $data, $server, $item); } else { @@ -533,32 +654,31 @@ sub nickserv_runsub { } } -load_nickservnet("$irssidir/$nickservnet_file"); -load_nickservnick("$irssidir/$nickservauth_file"); +load_nickservnet($nickservnet_file); +load_nickservnick($nickservauth_file); +load_nickservpostcmd($nickservpostcmd_file); Irssi::command_bind('nickserv', 'nickserv_runsub'); Irssi::command_bind('ns', 'nickserv_runsub'); -Irssi::command_bind('nickserv addnet', 'add_network'); -Irssi::command_bind('ns addnet', 'add_network'); - -Irssi::command_bind('nickserv addnick', 'add_nickname'); -Irssi::command_bind('ns addnick', 'add_nickname'); - -Irssi::command_bind('nickserv listnet', 'list_net'); -Irssi::command_bind('ns listnet', 'list_net'); - -Irssi::command_bind('nickserv listnick', 'list_nick'); -Irssi::command_bind('ns listnick', 'list_nick'); - -Irssi::command_bind('nickserv delnet', 'del_network'); -Irssi::command_bind('ns delnet', 'del_network'); - -Irssi::command_bind('nickserv delnick', 'del_nickname'); -Irssi::command_bind('ns delnick', 'del_nickname'); - Irssi::command_bind('nickserv help' => sub { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_help', $help) }); Irssi::command_bind('ns help' => sub { Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_help', $help) }); +# "command binding" -> "function name" mapping +for my $cmd (( + ['addnet' => 'add_network'], + ['addnick' => 'add_nickname'], + ['addpostcmd' => 'add_postcmd'], + ['listnet' => 'list_net'], + ['listnick' => 'list_nick'], + ['listpostcmd' => 'list_postcmd'], + ['delnet' => 'del_network'], + ['delnick' => 'del_nickname'], + ['delpostcmd' => 'del_postcmd'], +)) { + Irssi::command_bind("nickserv $cmd->[0]", $cmd->[1]); + Irssi::command_bind("ns $cmd->[0]", $cmd->[1]); +} + Irssi::signal_add('event notice', 'nickserv_notice'); Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'nickserv_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors}); diff --git a/scripts/pager.pl b/scripts/pager.pl index 32f0902..50d7899 100644 --- a/scripts/pager.pl +++ b/scripts/pager.pl @@ -2,14 +2,16 @@ use strict; use Irssi 20020121.2020 (); -$VERSION = "1.1"; + +use vars qw/$VERSION %IRSSI/; +$VERSION = "1.2"; %IRSSI = ( authors => 'Jean-Yves Lefort', contact => 'jylefort\@brutele.be', name => 'pager', description => 'Notifies people if they send you a private message or a DCC chat offer while you are away; runs a shell command configurable via /set if they page you', license => 'BSD', - changed => '$Date: 2003/01/27 09:45:16 $ ', + changed => '$Date: 2017/03/06 $ ', ); # note: @@ -33,6 +35,9 @@ $VERSION = "1.1"; # # changes: # +# 2017-03-06 release 1.2 +# * declaration $VERSION %IRSSI +# # 2003-01-27 release 1.1 # * notices and commands are now optional # diff --git a/scripts/perlalias.pl b/scripts/perlalias.pl new file mode 100644 index 0000000..b546688 --- /dev/null +++ b/scripts/perlalias.pl @@ -0,0 +1,288 @@ +=head1 perlalias.pl - Perl-based command aliases for irssi + +This script provides an /alias-like function that uses small pieces of perl code to carry out the commands. + +=head2 Usage + +Install into irssi script directory and /run perlalias and/or put into autorun. + +=head2 Commands + +=over + +=item /perlalias + +Syntax: /perlalias [[[-]<alias>] [<code>]] + +Parameters: A name of the alias and the perl code to execute. + +If you prepend the alias with -, it will remove the alias. + +If you give no arguments, the list of defined aliases will be displayed. + +Description: + +Creates or updates an alias. Like any perl code, multiple statements must be separated using ; characters. +No replacement of parameter values is done: any $text is a perl variable. + +The arguments given to the /alias when typed are put into $_ and are also split on whitespace and put into @_. +In addition, the variables $server and $witem will refer to the active server and window item respectively. + +Examples: + +/PERLALIAS UNACT foreach my $w (Irssi::windows) { $w->activity(0); } + +=back + +=over + +=item /perlunalias + +Syntax: /perlunalias <alias> + +Parameters: The alias to remove. + +Description: + +Removes the given alias. + +=back + +Additionally, all aliases added are linked to perlalias.pl: if it is unloaded, the aliases will be removed. + +Aliases can be saved and reloaded with the usual /save and /reload (including autosave). Saved aliases are loaded at script load. + +=head2 ChangeLog + +=over + +=item 1.0 + +First version. + +=back + +=cut + +use strict; +use warnings FATAL => qw(all); +use Irssi; +use Irssi::Irc; +use Carp (); + +#use Cwd; +use POSIX qw(strftime); + +{ package Irssi::Nick; } # Keeps trying to look for this package but for some reason it doesn't get loaded. + +our $VERSION = '1.2'; +our %IRSSI = ( + authors => 'aquanight', + contact => 'aquanight@gmail.com', + name => 'perlalias', + description => 'Quickly create commands from short perl blocks', + license => 'public domain' + ); + +# Bound commands +my %cmds; # Contains command entries. The entry has three items: + # textcmd => Plaintext of the command to execute, which is used for loading/saving + # cmpcmd => Compiled command, for executing. + # tag => Our tag which we need to remove the command + +# Package we execute all the commands within, to keep them away from our bits. +package Irssi::Script::perlalias::aliaspkg { +} + +sub DESTROY { + Symbol::delete_package("Irssi::Script::perlalias::aliaspkg::"); +} + +# Alias executor +sub exec_perlalias { + my ($cmd, $data, $server, $witem) = @_; + exists $cmds{$cmd} or return; + defined $cmds{$cmd}->{cmpcmd} or return; + local $_ = $data; + $cmds{$cmd}->{cmpcmd}->($server, $witem, split / +/, $data); +} + +# Bind a command +sub setup_command { + my ($cmd, $data) = @_; + # Compile the script. + my $code = qq{package Irssi::Scripts::perlalias::aliaspkg;\nno warnings;\nsub {my \$server = shift; my \$witem = shift;\n#line 1 "perlalias $cmd"\n$data}\n}; + my $proc = eval $code; + if ($@) { + Irssi::printformat(MSGLEVEL_CLIENTERROR, perlalias_compile_error => $cmd); + Irssi::print(MSGLEVEL_CLIENTERROR, $@); + return ""; + } + if (exists($cmds{$cmd})) { + my $entry = $cmds{$cmd}; + $entry->{textcmd} = $data; + $entry->{cmpcmd} = $proc; + } + else { + my $entry = {}; + my $tag = sub { exec_perlalias $cmd, @_; }; + foreach my $existing_cmd (Irssi::commands()) { + if ($existing_cmd->{cmd} eq $cmd) { + Irssi::print_format(MSGLEVEL_CLIENTERROR, perlalias_cmd_in_use => $cmd); + return ""; + } + } + $entry->{textcmd} = $data; + $entry->{cmpcmd} = $proc; + $entry->{tag} = sub { exec_perlalias $cmd, @_; }; + Irssi::command_bind($cmd, $entry->{tag}); + $cmds{$cmd} = $entry; + } + return 1; +} + +sub remove_command { + my ($cmd) = @_; + if (exists($cmds{$cmd})) { + my $entry = $cmds{$cmd}; + $entry->{tag}//die "Missing the tag we need to remove the alias!!!"; + Irssi::command_unbind($cmd, $entry->{tag}); + delete $cmds{$cmd}; + return 1; + } + else { + Irssi::printformat(MSGLEVEL_CLIENTERROR, perlalias_not_found => $cmd); + return ""; + } +} + +sub list_commands { + my ($prefix) = @_; + my @whichones = sort grep /^\Q$prefix\E/, keys %cmds; + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'perlaliaslist_header'); + for my $name (@whichones) { + my $entry = $cmds{$name}; + Irssi::printformat(MSGLEVEL_CLIENTCRAP, perlaliaslist_line => $name, $entry->{textcmd}); + } +} + +sub cmd_perlalias { + my ($data, $server, $witem) = @_; + my ($command, $script) = split /\s+/, $data, 2; + if (($command//"") eq "") { + list_commands ""; + } + elsif ($command =~ m/^-/) { + $command = substr($command, 1); + if (remove_command($command)) { Irssi::printformat(MSGLEVEL_CLIENTNOTICE, perlalias_removed => $command); } + } + elsif (($script//"") eq "") { + list_commands $command; + } + else { + if (setup_command($command, $script)) { Irssi::printformat(MSGLEVEL_CLIENTNOTICE, perlalias_added => $command); } + } + +} + +sub cmd_perlunalias { + my ($data, $server, $witem) = @_; + if (remove_command $data) { Irssi::printformat(MSGLEVEL_CLIENTNOTICE, perlalias_removed => $data); } +} + +sub sig_setup_saved { + my ($main, $auto) = @_; + my $file = Irssi::get_irssi_dir() . "/perlalias"; + open my $fd, '>', $file or return; + for my $cmd (keys %cmds) { + my $entry = $cmds{$cmd}; + printf $fd "%s\t%s\n", $cmd, $entry->{textcmd}; + } + close $fd; +} + +sub sig_setup_reread { + my $file = Irssi::get_irssi_dir() . "/perlalias"; + open my $fd, "<", $file or return; + my $ln; + my %newcmds; + while (defined($ln = <$fd>)) { + chomp $ln; + my ($cmd, $script) = split /\t/, $ln, 2; + if (exists $newcmds{$cmd}) { + Irssi::print(MSGLEVEL_CLIENTERROR, "There is a duplicate record in the PerlAlias save file."); + Irssi::print(MSGLEVEL_CLIENTERROR, "Offending alias: $cmd"); + Irssi::print(MSGLEVEL_CLIENTERROR, "Previous definition: " . $newcmds{$cmd}); + Irssi::print(MSGLEVEL_CLIENTERROR, "Duplicate definition: $script"); + } + $newcmds{$cmd} = $script; + } + # Scrub the existing list. Update existings, remove any that aren't in the config, then we'll add any that's new. + my @currentcmds = keys %cmds; + for my $cmd (@currentcmds) { + if (exists $newcmds{$cmd}) { + setup_command($cmd, $newcmds{$cmd}); + } + else { + remove_command($cmd); + } + delete $newcmds{$cmd}; + } + # By this point all that should be in newcmds is any ... new commands. + for my $cmd (keys %newcmds) { + setup_command($cmd, $newcmds{$cmd}); + } + close $fd; +} + +sub sig_complete_perlalias { + my ($lst, $win, $word, $line, $want_space) = @_; + $word//return; + $line//return; + $lst//return; + if ($line ne '') { + my $def = $cmds{$line}; + $def//return; + push @$lst, $def->{textcmd}; + Irssi::signal_stop(); + } + else { + push @$lst, (grep /^\Q$word\E/i, keys %cmds); + Irssi::signal_stop(); + } +} + +sub sig_complete_perlunalias { + my ($lst, $win, $word, $line, $want_space) = @_; + $lst//return; + $word//return; + push @$lst, (grep /^\Q$word\E/i, keys %cmds); +} + +Irssi::signal_register({"complete command " => [qw(glistptr_char* Irssi::UI::Window string string intptr)]}); +Irssi::signal_add("complete command perlalias" => \&sig_complete_perlalias); +Irssi::signal_add("complete command perlunalias" => \&sig_complete_perlunalias); + +Irssi::signal_add("setup saved" => \&sig_setup_saved); +Irssi::signal_add("setup reread" => \&sig_setup_reread); + +Irssi::command_bind(perlalias => \&cmd_perlalias); +Irssi::command_bind(perlunalias => \&cmd_perlunalias); + +my %formats = ( + # $0 Name of alias + 'perlalias_compile_error' => '{error Error compiling alias {hilight $0}:}', + # $0 Name of alias + 'perlalias_exec_error' => '{error Error executing alias {hilight $0}:}', + 'perlalias_cmd_in_use' => 'Command {hilight $0} is already in use', + 'perlalias_added' => 'PerlAlias {hilight $0} added', + 'perlalias_removed' => 'PerlAlias {hilight $0} removed', + 'perlalias_not_found' => 'PerlAlias {hilight $0} not found', + 'perlaliaslist_header' => '%#PerlAliases:', + # $0 Name of alias, $1 alias text + 'perlaliaslist_line' => '%#$[10]0 $1', +); + +Irssi::theme_register([%formats]); + +sig_setup_reread; diff --git a/scripts/postpone.pl b/scripts/postpone.pl index 72e0a90..11011f2 100644 --- a/scripts/postpone.pl +++ b/scripts/postpone.pl @@ -5,7 +5,7 @@ use strict; use vars qw($VERSION %IRSSI); -$VERSION = "20030208"; +$VERSION = "20170204"; %IRSSI = ( authors => "Stefan 'tommie' Tomanek", contact => "stefan\@pico.ruhr.de", @@ -37,6 +37,8 @@ sub show_help() { Display this help /postpone flush <nick> Flush postponed messages to <nick> +/postpone discard <nick> + Discard postponed messages to <nick> /postpone list List postponed messages "; @@ -85,11 +87,11 @@ sub cmd_postpone ($$$) { my @arg = split(/ /, $args); if (scalar(@arg) < 1) { #foo - } elsif ($arg[0] eq 'flush' && defined $arg[1]) { + } elsif (($arg[0] eq 'discard' || $arg[0] eq 'flush') && defined $arg[1]) { return unless ($witem && $witem->{type} eq "CHANNEL"); while (scalar(@{$messages{$server->{tag}}{$witem->{name}}{$arg[1]}}) > 0) { my $msg = pop @{$messages{$server->{tag}}{$witem->{name}}{$arg[1]}}; - $server->command('MSG '.$witem->{name}.' '.$msg); + $server->command('MSG '.$witem->{name}.' '.$msg) if $arg[0] eq 'flush'; } } elsif ($arg[0] eq 'list') { my $text; diff --git a/scripts/print_signals.pl b/scripts/print_signals.pl new file mode 100644 index 0000000..dadd9eb --- /dev/null +++ b/scripts/print_signals.pl @@ -0,0 +1,280 @@ +# print_signals.pl — Irssi script to help with inspecting signals +# +# © 2017 martin f. krafft <madduck@madduck.net> +# Released under the MIT licence. +# +### Usage: +# +# /script load print_signals +# +# and then use e.g. tail -F /tmp/irssi_signals.log outside of irssi. +# +### Settings: +# +# /set print_signals_to_file ["/tmp/irssi_signals.log"] +# Set the file to which to log all signals and their data +# +# /set print_signals_limit_regexp [""] +# Specify a regexp to limit the signals being captured, e.g. "^window". +# Default is no limit. +# +# # Please note that exclude takes precedence over limit: +# +# /set print_signals_exclude_regexp ["print text|key press|textbuffer"] +# Specify a regexp to exclude signals from being captured. Default is not to +# fire on signals about printing text or key presses. +# + +use strict; +use warnings; +use vars qw($VERSION %IRSSI); +use Irssi; +use Data::Dumper; + +$VERSION = '1.0'; + +%IRSSI = ( + authors => 'martin f. krafft', + contact => 'madduck@madduck.net', + name => 'print signals debugger', + description => 'hooks into every signal and writes the information provided to a file', + license => 'MIT', + changed => '2017-02-03' +); + +Irssi::settings_add_str('print_signals', 'print_signals_to_file', '/tmp/irssi_signals.log'); +Irssi::settings_add_str('print_signals', 'print_signals_limit_regexp', ''); +Irssi::settings_add_str('print_signals', 'print_signals_exclude_regexp', + 'print text|key press|textbuffer|rawlog|log written'); + +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Pad = ' '; + +sub signal_handler { + my $signal = shift(@_); + my $limitre = Irssi::settings_get_str('print_signals_limit_regexp'); + return unless $signal =~ qr/$limitre/; + my $excludere = Irssi::settings_get_str('print_signals_exclude_regexp'); + return if $signal =~ qr/$excludere/; + my @names = shift(@_); + my @data = shift(@_); + my $outfile = Irssi::settings_get_str('print_signals_to_file'); + my $fh; + if (!open($fh, '>>', $outfile)) { + Irssi::print("cannot append to log file $outfile while handling signal '$signal'"); + return; + }; + print $fh "\n== $signal ==\n"; + print $fh Data::Dumper->Dump(@data, @names); + close($fh); +} + +# TODO: a programmatic way to extract the list of all signals from Irssi +# itself, along with descriptive names of the arguments. +my $signals = <<_END; +# curl -s https://raw.githubusercontent.com/irssi/irssi/master/docs/signals.txt | sed -rne 's,^ ",",p' +"gui exit" +"gui dialog", char *type, char *text +"send command", char *command, SERVER_REC, WI_ITEM_REC +"chat protocol created", CHAT_PROTOCOL_REC +"chat protocol updated", CHAT_PROTOCOL_REC +"chat protocol destroyed", CHAT_PROTOCOL_REC +"channel created", CHANNEL_REC, int automatic +"channel destroyed", CHANNEL_REC +"chatnet created", CHATNET_REC +"chatnet destroyed", CHATNET_REC +"commandlist new", COMMAND_REC +"commandlist remove", COMMAND_REC +"error command", int err, char *cmd +"send command", char *args, SERVER_REC, WI_ITEM_REC +"send text", char *line, SERVER_REC, WI_ITEM_REC +"command "<cmd>, char *args, SERVER_REC, WI_ITEM_REC +"default command", char *args, SERVER_REC, WI_ITEM_REC +"ignore created", IGNORE_REC +"ignore destroyed", IGNORE_REC +"ignore changed", IGNORE_REC +"log new", LOG_REC +"log remove", LOG_REC +"log create failed", LOG_REC +"log locked", LOG_REC +"log started", LOG_REC +"log stopped", LOG_REC +"log rotated", LOG_REC +"log written", LOG_REC, char *line +"module loaded", MODULE_REC, MODULE_FILE_REC +"module unloaded", MODULE_REC, MODULE_FILE_REC +"module error", int error, char *text, char *rootmodule, char *submodule +"tls handshake finished", SERVER_REC, TLS_REC +"nicklist new", CHANNEL_REC, NICK_REC +"nicklist remove", CHANNEL_REC, NICK_REC +"nicklist changed", CHANNEL_REC, NICK_REC, char *old_nick +"nicklist host changed", CHANNEL_REC, NICK_REC +"nicklist gone changed", CHANNEL_REC, NICK_REC +"nicklist serverop changed", CHANNEL_REC, NICK_REC +"pidwait", int pid, int status +"query created", QUERY_REC, int automatic +"query destroyed", QUERY_REC +"query nick changed", QUERY_REC, char *orignick +"window item name changed", WI_ITEM_REC +"query address changed", QUERY_REC +"query server changed", QUERY_REC, SERVER_REC +"rawlog", RAWLOG_REC, char *data +"server looking", SERVER_REC +"server connected", SERVER_REC +"server connecting", SERVER_REC, ulong *ip +"server connect failed", SERVER_REC +"server disconnected", SERVER_REC +"server quit", SERVER_REC, char *msg +"server sendmsg", SERVER_REC, char *target, char *msg, int target_type +"setup changed" +"setup reread", char *fname +"setup saved", char *fname, int autosaved +"ban type changed", char *bantype +"channel joined", CHANNEL_REC +"channel wholist", CHANNEL_REC +"channel sync", CHANNEL_REC +"channel topic changed", CHANNEL_REC +"ctcp msg", SERVER_REC, char *args, char *nick, char *addr, char *target +"ctcp msg "<cmd>, SERVER_REC, char *args, char *nick, char *addr, char *target +"default ctcp msg", SERVER_REC, char *args, char *nick, char *addr, char *target +"ctcp reply", SERVER_REC, char *args, char *nick, char *addr, char *target +"ctcp reply "<cmd>, SERVER_REC, char *args, char *nick, char *addr, char *target +"default ctcp reply", SERVER_REC, char *args, char *nick, char *addr, char *target +"ctcp action", SERVER_REC, char *args, char *nick, char *addr, char *target +"awaylog show", LOG_REC, int away_msgs, int filepos +"server nick changed", SERVER_REC +"event connected", SERVER_REC +"server cap ack "<cmd>, SERVER_REC +"server cap nak "<cmd>, SERVER_REC +"server cap end", SERVER_REC +"server sasl failure", SERVER_REC, char *reason +"server sasl success", SERVER_REC +"server event", SERVER_REC, char *data, char *sender_nick, char *sender_address +"event "<cmd>, SERVER_REC, char *args, char *sender_nick, char *sender_address +"default event", SERVER_REC, char *data, char *sender_nick, char *sender_address +"whois default event", SERVER_REC, char *args, char *sender_nick, char *sender_address +"server incoming", SERVER_REC, char *data +"redir "<cmd>, SERVER_REC, char *args, char *sender_nick, char *sender_address +"server lag", SERVER_REC +"server lag disconnect", SERVER_REC +"massjoin", CHANNEL_REC, GSList of NICK_RECs +"ban new", CHANNEL_REC, BAN_REC +"ban remove", CHANNEL_REC, BAN_REC, char *setby +"channel mode changed", CHANNEL_REC, char *setby +"nick mode changed", CHANNEL_REC, NICK_REC, char *setby, char *mode, char *type +"user mode changed", SERVER_REC, char *old +"away mode changed", SERVER_REC +"netsplit server new", SERVER_REC, NETSPLIT_SERVER_REC +"netsplit server remove", SERVER_REC, NETSPLIT_SERVER_REC +"netsplit new", NETSPLIT_REC +"netsplit remove", NETSPLIT_REC +"dcc ctcp "<cmd>, char *args, DCC_REC +"default dcc ctcp", char *args, DCC_REC +"dcc unknown ctcp", char *args, char *sender, char *sendaddr +"dcc reply "<cmd>, char *args, DCC_REC +"default dcc reply", char *args, DCC_REC +"dcc unknown reply", char *args, char *sender, char *sendaddr +"dcc chat message", DCC_REC, char *msg +"dcc created", DCC_REC +"dcc destroyed", DCC_REC +"dcc connected", DCC_REC +"dcc rejecting", DCC_REC +"dcc closed", DCC_REC +"dcc request", DCC_REC, char *sendaddr +"dcc request send", DCC_REC +"dcc chat message", DCC_REC, char *msg +"dcc transfer update", DCC_REC +"dcc get receive", DCC_REC +"dcc error connect", DCC_REC +"dcc error file create", DCC_REC, char *filename +"dcc error file open", char *nick, char *filename, int errno +"dcc error get not found", char *nick +"dcc error send exists", char *nick, char *filename +"dcc error unknown type", char *type +"dcc error close not found", char *type, char *nick, char *filename +"autoignore new", SERVER_REC, AUTOIGNORE_REC +"autoignore remove", SERVER_REC, AUTOIGNORE_REC +"flood", SERVER_REC, char *nick, char *host, int level, char *target +"notifylist new", NOTIFYLIST_REC +"notifylist remove", NOTIFYLIST_REC +"notifylist joined", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg +"notifylist away changed", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg +"notifylist left", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg +"proxy client connecting", CLIENT_REC +"proxy client connected", CLIENT_REC +"proxy client disconnected", CLIENT_REC +"proxy client command", CLIENT_REC, char *args, char *data +"proxy client dump", CLIENT_REC, char *data +"gui print text", WINDOW_REC, int fg, int bg, int flags, char *text, TEXT_DEST_REC +"gui print text finished", WINDOW_REC +"complete word", GList * of char*, WINDOW_REC, char *word, char *linestart, int *want_space +"irssi init read settings" +"exec new", PROCESS_REC +"exec remove", PROCESS_REC, int status +"exec input", PROCESS_REC, char *text +"message public", SERVER_REC, char *msg, char *nick, char *address, char *target +"message private", SERVER_REC, char *msg, char *nick, char *address, char *target +"message own_public", SERVER_REC, char *msg, char *target +"message own_private", SERVER_REC, char *msg, char *target, char *orig_target +"message join", SERVER_REC, char *channel, char *nick, char *address +"message part", SERVER_REC, char *channel, char *nick, char *address, char *reason +"message quit", SERVER_REC, char *nick, char *address, char *reason +"message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason +"message nick", SERVER_REC, char *newnick, char *oldnick, char *address +"message own_nick", SERVER_REC, char *newnick, char *oldnick, char *address +"message invite", SERVER_REC, char *channel, char *nick, char *address +"message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address +"keyinfo created", KEYINFO_REC +"keyinfo destroyed", KEYINFO_REC +"print text", TEXT_DEST_REC *dest, char *text, char *stripped +"theme created", THEME_REC +"theme destroyed", THEME_REC +"window hilight", WINDOW_REC +"window dehilight", WINDOW_REC +"window activity", WINDOW_REC, int old_level +"window item hilight", WI_ITEM_REC +"window item activity", WI_ITEM_REC, int old_level +"window item new", WINDOW_REC, WI_ITEM_REC +"window item remove", WINDOW_REC, WI_ITEM_REC +"window item moved", WINDOW_REC, WI_ITEM_REC, WINDOW_REC +"window item changed", WINDOW_REC, WI_ITEM_REC +"window item server changed", WINDOW_REC, WI_ITEM_REC +"window created", WINDOW_REC +"window destroyed", WINDOW_REC +"window changed", WINDOW_REC, WINDOW_REC old +"window changed automatic", WINDOW_REC +"window server changed", WINDOW_REC, SERVER_REC +"window refnum changed", WINDOW_REC, int old +"window name changed", WINDOW_REC +"window history changed", WINDOW_REC, char *oldname +"window level changed", WINDOW_REC +"default event numeric", SERVER_REC, char *data, char *nick, char *address +"message irc op_public", SERVER_REC, char *msg, char *nick, char *address, char *target +"message irc own_wall", SERVER_REC, char *msg, char *target +"message irc own_action", SERVER_REC, char *msg, char *target +"message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target +"message irc own_notice", SERVER_REC, char *msg, char *target +"message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target +"message irc own_ctcp", SERVER_REC, char *cmd, char *data, char *target +"message irc ctcp", SERVER_REC, char *cmd, char *data, char *nick, char *address, char *target +"message irc mode", SERVER_REC, char *channel, char *nick, char *addr, char *mode +"message dcc own", DCC_REC *dcc, char *msg +"message dcc own_action", DCC_REC *dcc, char *msg +"message dcc own_ctcp", DCC_REC *dcc, char *cmd, char *data +"message dcc", DCC_REC *dcc, char *msg +"message dcc action", DCC_REC *dcc, char *msg +"message dcc ctcp", DCC_REC *dcc, char *cmd, char *data +"gui key pressed", int key +"beep" +"gui print text after finished", WINDOW_REC, LINE_REC *line, LINE_REC *prev_line +"gui textbuffer line removed", TEXTBUFFER_VIEW_REC *view, LINE_REC *line, LINE_REC *prev_line +_END + +foreach my $sigline (split(/\n/, $signals)) { + my ($sig, @args) = split(/, /, $sigline); + $sig =~ y/"//d; + Irssi::signal_add_first($sig, sub { + signal_handler($sig, \@args, \@_); + } + ); +}; diff --git a/scripts/reorder.pl b/scripts/reorder.pl index 7bec6b8..ce82fa4 100644 --- a/scripts/reorder.pl +++ b/scripts/reorder.pl @@ -1,150 +1,161 @@ -# Save window layout to an arbitrary file and load layouts upon demand
-# Useful for being able to temporarily reorder your windows and then reverting to your "normal" layout
-# Also useful as an easy way to reorder your windows
-#
-# A special thanks to billnye, Zed` and Bazerka for their help
-#
-# Usage:
-# /layout_save filename
-# Saves the layout to the textfile "filename.layout"
-# /layout_load filename
-# Loads the layout from the textfile "filename.layout"
-# /set layout_savepath path
-# Use to set a default path for layouts
-#
-# TODO:
-# Check the layout file for a number used twice
-#
-
-use strict;
-use Irssi;
-use Data::Dumper;
-use vars qw($VERSION %IRSSI);
-
-%IRSSI = (
- authors => "Isaac G",
- contact => "irssi\@isaac.otherinbox.com",
- name => "reorder",
- description => "Reordering windows based on a textfile.",
- license => "GPL",
-);
-
-sub doFilenameFixing
-{
- my ($filename) = @_;
- unless ($filename)
- {
- print "No filename specified!";
- return;
- }
-
- $filename = glob($filename);
-
- if ($filename =~ /\//)
- {
- unless ($filename =~ /^\//)
- {
- print "I don't like /'s in filenames. Unless you want to specify an absolute path.";
- return;
- }
- # Let it go?
- }
-
- $filename .= '.layout' unless ($filename =~ /.layout$/);
-
- my $path = Irssi::settings_get_str('layout_savepath');
- $path .= '/' unless ($path =~ /\/$/);
- $filename = $path . $filename unless ($filename =~ /\//);
-
- return $filename;
-}
-
-sub canReadFile
-{
- my ($filename) = @_;
- unless (-f $filename)
- {
- print "No such file $filename";
- return;
- }
- unless (-r $filename)
- {
- print "Can not read file $filename";
- return;
- }
- return 1;
-}
-
-# Save a list of refnum and window info to file
-sub cmd_layout_save
-{
- my ($filename, $data, $more) = @_;
- my $FH;
-
- $filename = doFilenameFixing($filename);
- return unless ($filename);
-
- unless(open $FH, ">", $filename)
- {
- print "Can not open $filename";
- return;
- }
-
- # Order by ref. Print ref and an id tag
- for my $win (sort {$a->{'refnum'} <=> $b->{'refnum'}} Irssi::windows())
- {
- my $id = $win->{'name'} ? $win->{'name'} : $win->{'active'}->{'name'} . ":" . $win->{'active'}->{'server'}->{'tag'};
- printf $FH "%d\t%s\n", $win->{'refnum'}, $id;
- }
- close $FH;
- print "Layout saved to $filename";
-}
-
-# Load a list and use it to reorder
-sub cmd_layout_load
-{
- # Check filename supplied, exists and readable
- my ($filename, $data, $more) = @_;
- $filename = doFilenameFixing($filename);
- return unless ($filename);
-
- return unless canReadFile($filename);
-
- my @layout;
- my ($ref, $id, $FH);
-
- # Pull the refnum and id
- unless(open $FH, "<", $filename)
- {
- print "Can not open file $filename.";
- return;
- }
- while (my $line = <$FH>)
- {
- chomp $line;
- my ($ref, $id) = split(/\t/, $line, 2);
- next unless ($ref and $id);
-
- push @layout, {refnum => $ref, id => $id};
- }
- close $FH;
-
- # For each layout item from the file, find the window and set it's ref to that number
- for my $position (sort {$a->{'refnum'} <=> $b->{'refnum'}} @layout)
- {
- for my $win (Irssi::windows())
- {
- $id = $win->{'name'} ? $win->{'name'} : $win->{'active'}->{'name'} . ":" . $win->{'active'}->{'server'}->{'tag'};
- if ($id eq $position->{'id'})
- {
- $win->set_refnum($position->{'refnum'});
- last;
- }
- }
- }
-}
-
-Irssi::settings_add_str('misc', 'layout_savepath', Irssi::get_irssi_dir());
-
-Irssi::command_bind( 'layout_save', 'cmd_layout_save' );
-Irssi::command_bind( 'layout_load', 'cmd_layout_load' );
\ No newline at end of file +# Save window layout to an arbitrary file and load layouts upon demand +# Useful for being able to temporarily reorder your windows and then reverting to your "normal" layout +# Also useful as an easy way to reorder your windows +# +# A special thanks to billnye, Zed` and Bazerka for their help +# +# Usage: +# /layout_save filename +# Saves the layout to the textfile "filename.layout" +# /layout_load filename +# Loads the layout from the textfile "filename.layout" +# +# TODO: +# Check the layout file for a number used twice +# On script load, run a layout_load +# On channel join, run load: channel joined +# + +use strict; +use Irssi; +use Data::Dumper; +use vars qw($VERSION %IRSSI); +use POSIX 'strftime'; + +%IRSSI = ( + authors => "Isaac Good", + contact => "irssi\@isaacgood.com", + name => "reorder", + description => "Reordering windows based on a textfile.", + license => "GPL", +); +$VERSION = '1.0'; + +# Map user input to a valid filename +sub GetFilename +{ + my ($filename) = @_; + + # On no input, use a default filename. + unless (length($filename)) + { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + $filename = POSIX::strftime("%y%m%d", $sec, $min, $hour, $mday, $mon, $year); + # If you prefer not having datestamped filenames, uncomment: + # $filename = "default"; + } + + # Use glob expansion to match things like ~/ + my $glob = glob($filename); + $filename = $glob if $glob; + + # Only handle directories when using an absolute path. + if ($filename =~ /\// and $filename !~ /^\//) + { + print "I don't like /'s in filenames. Unless you want to specify an absolute path."; + return; + } + + # Add a file extension + $filename .= '.layout' unless ($filename =~ /\.layout$/); + + # Use get_irssi_dir() unless using an absolute path + if ($filename !~ /\//) { + my $path = Irssi::get_irssi_dir(); + $path .= '/' unless ($path =~ /\/$/); + $filename = $path . $filename; + } + + return $filename; +} + +# Check a filename exists and can be read. +sub CanReadFile +{ + my ($filename) = @_; + unless (-f $filename) + { + print "No such file $filename"; + return 0; + } + unless (-r $filename) + { + print "Can not read file $filename"; + return 0; + } + return 1; +} + +# Save the current layout to file +sub CmdLayoutSave +{ + my ($filename, $data, $more) = @_; + my $FH; + + $filename = GetFilename($filename); + return unless ($filename); + + unless(open $FH, ">", $filename) + { + print "Can not open $filename"; + return; + } + + # Order by ref. Print ref and an id tag + for my $win (sort {$a->{'refnum'} <=> $b->{'refnum'}} Irssi::windows()) + { + my $id = $win->{'name'} ? $win->{'name'} : $win->{'active'}->{'name'}; + my $tag = $win->{'active'}->{'server'}->{'tag'}; + printf $FH "%d\t%s:%s\n", $win->{'refnum'}, $id, $tag; + } + close $FH; + print "Layout saved to $filename"; +} + +# Load a list and use it to reorder +sub CmdLayoutLoad +{ + my ($filename, $data, $more) = @_; + $filename = GetFilename($filename); + + return unless ($filename); + return unless CanReadFile($filename); + + my @layout; + my ($ref, $id, $tag, $FH); + + # Pull the refnum and id + unless(open $FH, "<", $filename) + { + print "Can not open file $filename."; + return; + } + while (my $line = <$FH>) + { + chomp $line; + my ($ref, $id) = split(/\t/, $line, 2); + next unless ($ref and $id); + + push @layout, {refnum => $ref, id => $id}; + } + close $FH; + + # For each layout item from the file, find the window and set it's ref to that number + for my $position (sort {$a->{'refnum'} <=> $b->{'refnum'}} @layout) + { + for my $win (Irssi::windows()) + { + $id = $win->{'name'} ? $win->{'name'} : $win->{'active'}->{'name'}; + $tag = $win->{'active'}->{'server'}->{'tag'}; + $id .= ":" . $tag; + if ($id eq $position->{'id'}) + { + $win->set_refnum($position->{'refnum'}); + last; + } + } + } +} + +Irssi::command_bind( 'layout_save', 'CmdLayoutSave' ); +Irssi::command_bind( 'layout_load', 'CmdLayoutLoad' ); diff --git a/scripts/rud_emotes.pl b/scripts/rud_emotes.pl index e783d75..de55cba 100644 --- a/scripts/rud_emotes.pl +++ b/scripts/rud_emotes.pl @@ -1,4 +1,4 @@ -# Copyright (C) 2015 Dawid Lekawski +# Copyright (C) 2016 Dawid Lekawski # contact: xxrud0lf@gmail.com # # --- INFORMATION --- @@ -33,7 +33,7 @@ # notes: # # - script doesn't work with /msg target text; must be typed in a channel -# or query window +# or query window (from version 1.10 it works with /me command too) # # - Ctrl+O (ascii 15) at the beggining of your text turns off emote replacing # for this text @@ -41,20 +41,31 @@ # - remeber to escape "\" characters in emotes (just type it twice -> "\\"), # take a look at 'shrug' emote for reference # +# +# +# -- CHANGES: -- +# +# - script now works with /me command (action) +# +# - moved text output messages into nice and clean theme_register +# +# use strict; use warnings; use utf8; -use Irssi qw(signal_add signal_continue command_bind); +use Irssi qw(signal_add signal_continue command_bind theme_register + printformat); -our $VERSION = "1.00"; +our $VERSION = "1.10"; our %IRSSI = ( authors => "Dawid 'rud0lf' Lekawski", contact => 'rud0lf/IRCnet; rud0lf/freenode; xxrud0lf@gmail.com', name => 'emotes script', description => 'Replaces :emote_name: text in your sent messages into pre-defined emotes (unicode mostly).', - license => 'GPLv3' + license => 'GPLv3', + changed => 'Mon Nov 07 14:54:38 2016' ); my $pattern = ''; @@ -94,10 +105,18 @@ my %emotes = ( 'wink', '◕‿↼', 'gift', '(´・ω・)っ由', 'success', '(•̀ᴗ•́)و', - 'whatever', '◔_◔' + 'whatever', '◔_◔', + 'run', 'ᕕ(⚆ ʖ̯⚆)ᕗ', + 'rock', '(ツ)\m/' ); sub init { + theme_register([ + 'rud_emotes_list', 'List of emotes:', + 'rud_emotes_emote', '* $[!15]0 : $1', + 'rud_emotes_total', 'Total of $0 emotes.' +]); + $pattern = join('|', keys %emotes); if ($pattern eq '') { $pattern = '!?'; @@ -127,31 +146,28 @@ sub sig_send_text { signal_continue($newline, $server, $witem); } -sub pad { - my ($txt, $cnt) = @_; +sub sig_command_me { + my ($line, $server, $witem) = @_; - if (length($txt) >= $cnt) { - return $txt; - } - - $txt .= " " x ($cnt - length($txt)); - return $txt; + return unless ($witem); + return unless ($witem->{type} eq "CHANNEL" or $witem->{type} eq "QUERY"); + + my $newline = process_emotes($line); + signal_continue($newline, $server, $witem); } sub cmd_emotes { my ($data, $server, $witem) = @_; - - Irssi::print('List of emotes:', MSGLEVEL_CLIENTCRAP); + + printformat(MSGLEVEL_CLIENTCRAP, 'rud_emotes_list'); foreach my $key (sort(keys %emotes)) { - my $emote = $emotes{$key}; - Irssi::print('* '. pad($key, 15) . ' : ' . $emote, MSGLEVEL_CLIENTCRAP); + printformat(MSGLEVEL_CLIENTCRAP, 'rud_emotes_emote', $key, $emotes{$key}); } - Irssi::print('Total of '.scalar(keys %emotes).' emotes.', MSGLEVEL_CLIENTCRAP); + printformat(MSGLEVEL_CLIENTCRAP, 'rud_emotes_total', scalar(keys %emotes)); } init(); signal_add("send text", "sig_send_text"); +signal_add("command me", "sig_command_me"); command_bind("emotes", "cmd_emotes"); - - diff --git a/scripts/trackbar22.pl b/scripts/trackbar22.pl index 30015a2..6c899b6 100644 --- a/scripts/trackbar22.pl +++ b/scripts/trackbar22.pl @@ -96,7 +96,7 @@ use Encode; use POSIX qw(strftime); use vars qw($VERSION %IRSSI); -$VERSION = "2.2"; # cb3189a33c8e5f9 +$VERSION = "2.3"; # 45c0adad4366edd %IRSSI = ( authors => 'Peter Leurs and Geert Hauwaerts', @@ -106,7 +106,6 @@ $VERSION = "2.2"; # cb3189a33c8e5f9 description => 'Shows a bar where you have last read a window.', license => 'GNU General Public License', url => 'http://www.pfoe.be/~peter/trackbar/', - changed => 'Fri Jan 23 23:59:11 2004', commands => 'trackbar', ); @@ -234,6 +233,7 @@ sub add_one_trackbar { $win->print(line($win->{width}), MSGLEVEL_NEVER); $view->set_bookmark_bottom('trackbar'); $unseen_trackbar{ $win->{_irssi} } = 1; + Irssi::signal_emit("window trackbar added", $win); $view->redraw; } @@ -253,6 +253,7 @@ sub win_ignored { my $view = shift || $win->view; return 1 unless $view->{buffer}{lines_count}; return 1 if $win->{name} eq '(status)' && !$config{use_status_window}; + no warnings 'uninitialized'; return 1 if grep { $win->{name} eq $_ || $win->{refnum} eq $_ || $win->get_active_name eq $_ } @{ $config{ignore_windows} }; return 0; @@ -271,10 +272,13 @@ sub sig_window_changed { sub trackbar_update_seen { my $win = shift; return unless $win; + return unless $unseen_trackbar{ $win->{_irssi} }; + my $view = $win->view; my $line = $view->get_bookmark('trackbar'); unless ($line) { delete $unseen_trackbar{ $win->{_irssi} }; + Irssi::signal_emit("window trackbar seen", $win); return; } my $startline = $view->{startline}; @@ -283,6 +287,7 @@ sub trackbar_update_seen { if ($startline->{info}{time} < $line->{info}{time} || $startline->{_irssi} == $line->{_irssi}) { delete $unseen_trackbar{ $win->{_irssi} }; + Irssi::signal_emit("window trackbar seen", $win); } } @@ -477,6 +482,8 @@ update_config(); Irssi::signal_add_last( 'mainwindow resized' => 'redraw_trackbars') unless $old_irssi; +Irssi::signal_register({'window trackbar added' => [qw/Irssi::UI::Window/]}); +Irssi::signal_register({'window trackbar seen' => [qw/Irssi::UI::Window/]}); Irssi::signal_register({'gui page scrolled' => [qw/Irssi::UI::Window/]}); Irssi::signal_add_last('gui page scrolled' => 'trackbar_update_seen'); diff --git a/scripts/translit.pl b/scripts/translit.pl index 478dd50..0a01506 100644 --- a/scripts/translit.pl +++ b/scripts/translit.pl @@ -1,7 +1,8 @@ use strict; -use vars qw(%IRSSI); +use vars qw(%IRSSI $VERSION); use Irssi; +$VERSION = "0.1"; %IRSSI = ( authors => 'dreg', contact => 'dreg@fine.lv', diff --git a/scripts/xdcc_autoget.pl b/scripts/xdcc_autoget.pl index 16f4639..57bcf65 100644 --- a/scripts/xdcc_autoget.pl +++ b/scripts/xdcc_autoget.pl @@ -50,7 +50,7 @@ use File::Copy; use Try::Tiny; use vars qw($VERSION %IRSSI); -$VERSION = "2.0"; +$VERSION = "2.1"; %IRSSI = ( name => "autoget", description => "XDCC Autoget, for automated searching and downloading of xdcc packs", @@ -143,7 +143,7 @@ sub ag_list sub ag_initserver #init server { - Irssi::signal_remove("server connected", "ag_server"); + Irssi::signal_remove("server connected", "ag_initserver"); $statusbarmessage = "Connected"; $server = $_[0]; if (!$runningflag) {Irssi::timeout_add_once(5000, sub { &ag_run; }, []);} @@ -270,11 +270,11 @@ sub ag_search #searches bots for packs $msgflag[$botcounter] = 0; #unset message flag so that ag_skip knows no important message has arrived if($episodicflag) { - my $searchterm; + my $searchterm = $terms[$termcounter[$botcounter]]; my @words = split(/#/, $terms[$termcounter[$botcounter]]); my $ep = sprintf("%.2d", $episode[$botcounter]); if ($#words > 0){$searchterm = "$words[0]$ep$words[1]";} - else {$searchterm = "$words[0] $ep";} + elsif ($words[0] ne $searchterm) {$searchterm = "$words[0] $ep";} ag_message("msg $bots[$botcounter] $findprefix $searchterm" ); push(@{$totags[$botcounter]}, Irssi::timeout_add_once($botdelay * 1000, sub { ag_skip($botcounter); } , [])); @@ -323,9 +323,9 @@ sub ag_getpacks #if ($m =~ m{#(\d+):}) my @temp = split(/[#,]/, $message); #split up the message into 'words' my $timeoutscleared = 0; - my $newpackflag = 1; foreach my $m (@temp) #find packs (#[NUMBER]: format) { + my $newpackflag = 1; if ($m =~ m/(\d+):(.+)/) { if (!$timeoutscleared) #reset timeouts if any packs are found @@ -339,7 +339,7 @@ sub ag_getpacks #if ($m =~ m{#(\d+):}) my $filename = $2; $filename =~ tr/[ ']/[__]/; if ($n eq "$bots[$botcounter] $1" or $n eq $filename) {$newpackflag = 0;} - last if ($n eq "$bots[$botcounter] $1"); + last if ($n eq "$bots[$botcounter] $1" or $n eq $filename); } if($newpackflag){push(@{$packs[$botcounter]}, $1);} #push all new pack numbers to list of packs } @@ -650,7 +650,6 @@ sub ag_parserem #parses remove arguments for deletion from file sub ag_add #add search terms { - ag_server; my @args = quotewords('\s+', 0, $_[0]); #split arguments (words in brackets not seperated) if ($#args < 0) { @@ -664,7 +663,6 @@ sub ag_add #add search terms sub ag_rem #remove ssearch terms { - ag_server; my @args = quotewords('\s+', 0, $_[0]); if ($#args < 0) { @@ -678,7 +676,6 @@ sub ag_rem #remove ssearch terms sub ag_botadd #add bots { - ag_server; my @args = quotewords('\s+', 0, $_[0]); if ($#args < 0) { @@ -692,7 +689,6 @@ sub ag_botadd #add bots sub ag_botrem #remove bots { - ag_server; my @args = quotewords('\s+', 0, $_[0]); if ($#args < 0) { |
