if (word(2 $loadinfo()) != [pf]) { load -pf $word(1 $loadinfo()); return; }; ## We depend on 'reconnect_required' hook and '$serverctl(GET $server OPEN)', ## and '$serverctl(GET x NEXT_SERVER_IN_GROUP)' which are not available in ## previous versions. if (info(i) < 1999 || J !~ [EPIC5*]) { xecho -b EPIC5 (rev. 1999) or newer is required; return; }; load addset; package reconnect; ## Reconnect script for EPIC5. ## Written by zlonix@efnet with help of hop@efnet, public domain. ## Help with testing by skered@efnet. ## ## Version: 1.0 (September, 2021) ## - Initial roll-out ## ## Version 2.0 (Febrary, 2022) ## - Major rewrite of script logic - now we don't use infinite timers, ## but use re-scheduling - the timer schedules itself if appropriate. ## Idea by hop@efnet. ## ## Version 3.0 (September, 2022) ## - Uppon reconnect send one join line with all channels and keys instead ## of bunch separate joins. ## This script do basic reconnection procedure for you, it hooks on newly ## implemented hook 'reconnect_required', it is thrown when network connection ## experience problems. For more details read UPDATES document. ## ## The script doesn't have complicated logic, for example if you fiddle with ## your windows while being in process of reconnection (killing them for ## example, or changing server association) unexpected things may happen, ## channels be joined not in correct windows, or not joined at all, be aware of ## that. Also don't change server refnums while in reconnect process - we rely ## on its consistency. ## ## Do not connect to more than one server per group. It's recommended to have ## sane ircII.servers file in your IRCLIB environment variable, with groups and ## all of that (on disconnect the script tries to reconnect to the server ## $auto_reconnect_retries times before trying next server in the group, looping ## over when tried the last one), also, note that Libera.chat supports supplying ## your password for the account upon connect, through password token in server ## description so you don't need to use sasl_auth script anymore. For details ## about server description and its tokens visit ## http://help.epicsol.org/server_description. ## ## If you use group - reconnects will be made only to original server, ## no switching to other servers in the default group will happen. Reconnection ## will unconditionally abort after $auto_reconnect_retries. To reconnect ## manually use /reconnect command or change the server with /server. ## ## Another limitation is that the script won't catch 'Connection refused', or ## any DNS errors, since it's not considered as action for 'reconnect_required' ## hook. It means, that if you start the client, and it can't connect to the ## specified server, reconnect won't happen. ## ## All of this has been sacrificed for simplicity of the implementation. ## ## If you encounter any bugs - please save the log with '/lastlog -file ' ## and report it to #epic@efnet. ## TODO ## ## Currently there is not much testing done with servers, which have round ## robin DNS, like irc.libera.chat. addset auto_reconnect_delay int; set auto_reconnect_delay 75; addset auto_reconnect_join_delay int; set auto_reconnect_join_delay 5; addset auto_reconnect_delay_method bool; set auto_reconnect_delay_method off; addset auto_reconnect_retries int; set auto_reconnect_retries 5; addset auto_reconnect_try_other_servers bool; set auto_reconnect_try_other_servers on; addset auto_reconnect bool { if (*0 == [on]) { on #-server_state 100 '% % ACTIVE' { @ :group = serverctl(GET $0 GROUP); ## If we use default group for more than one server ## we need to encode server name as a tag for our ## counters and variables, since user may be connected ## to more than one server in default group, and when ## we will experience disconnect for more than one server, ## timers and variables will share encoded "" ## tag. ## ## We encode(), because default "" group ## isn't accepted as lval for auto_reconnect array if (group == []) { @ :group_enc = encode($serverctl(GET $0 NAME)); } else { @ :group_enc = encode($serverctl(GET $0 GROUP)); }; ## Stop reconnection timers on successful connect timer -delete auto_reconnect.$group_enc; ^assign -auto_reconnect.failures[$group_enc]; ## Send one line with all channels to join ## https://www.rfc-editor.org/rfc/rfc1459#section-4.2.1 @ :chans = auto_reconnect[$group_enc][chans]; @ :keys = auto_reconnect[$group_enc][keys]; if (@chans) { if (auto_reconnect_delay_method == [on]) { @i = auto_reconnect_join_delay; fe chans chan { timer $i quote join $chan; @i++; }; } else { quote join $sar(g/ /,/$chans) $sar(g/ /,/$keys); }; }; }; on #-channel_claim 100 * { @ :group = serverctl(GET $0 GROUP); if (group == []) { @ :group_enc = encode($serverctl(GET $0 NAME)); } else { @ :group_enc = encode($serverctl(GET $0 GROUP)); }; ## If we can't find the channel in auto_reconnect array for the ## group - we're not in reconnect situation, user just has joined ## a channel on his own - do not proceed. @ :idx = findw($1 $auto_reconnect[$group_enc][chans]); if (idx == -1) { return; }; @ :key = word($idx $auto_reconnect[$group_enc][keys]); @ :uuid = word($idx $auto_reconnect[$group_enc][wins]); @ :win = windowctl(REFNUM $uuid); if (windowctl(GET $win SERVER) == *0 && windowctl(GET $2 uuid) != uuid) { window $uuid claim $1; }; @ ::auto_reconnect[$group_enc][chans] = notw($idx $auto_reconnect[$group_enc][chans]); @ ::auto_reconnect[$group_enc][keys] = notw($idx $auto_reconnect[$group_enc][keys]); @ ::auto_reconnect[$group_enc][wins] = notw($idx $auto_reconnect[$group_enc][wins]); }; alias auto_reconnect.handler (server, void) { @ :server_name = serverctl(GET $server NAME); @ :group = serverctl(GET $server GROUP); if (group == []) { @ :group_enc = encode($serverctl(GET $server NAME)); } else { @ :group_enc = encode($serverctl(GET $server GROUP)); }; ## Check if we're reconnecting already if (timerctl(REFNUM auto_reconnect.$group_enc)) { return; }; if (!serverctl(GET $server OPEN)) { @ :attempt = (auto_reconnect.failures[$group_enc] % auto_reconnect_retries) + 1; @ auto_reconnect.failures[$group_enc]++; if (attempt <= auto_reconnect_retries && auto_reconnect.overflow != 1) { ## This is ugly hack for detecting when we must ## try next server in a group @ auto_reconnect.overflow = (attempt == auto_reconnect_retries); xecho -b Autoreconnecting to $server_name [group $group] - attempt $attempt; server $server; } else { @ auto_reconnect.overflow = 0; if (auto_reconnect_try_other_servers == [on] && group != []) { @ :old_server_name = serverctl(GET $server NAME); @ :server = serverctl(GET $server NEXT_SERVER_IN_GROUP); @ :new_server_name = serverctl(GET $server NAME); xecho -b Maximum auto reconnect retries to server $old_server_name has been reached; xecho -b Switching to the next server in group $group - $new_server_name - attempt $attempt; server $server; } else { xecho -b Maximum auto reconnect retries failed - Use /RECONNECT to reconnect; ^assign -auto_reconnect.failures[$group_enc]; return; }; }; } else { xecho -b Timeout for autoreconnect retry has been reached, but you're still in the process of connection; xecho -b Scheduling another try in $auto_reconnect_delay seconds; }; timer -server $server -refnum auto_reconnect.$group_enc $auto_reconnect_delay auto_reconnect.handler $server; }; on #-reconnect_required 100 * { @ :server = serverctl(GET $0 NAME); @ :group = serverctl(GET $0 GROUP); if (group == []) { @ :group_enc = encode($serverctl(GET $0 NAME)); } else { @ :group_enc = encode($serverctl(GET $0 GROUP)); }; xecho -b Connection to server $server [group $group] has been lost, autoreconnecting in $auto_reconnect_delay seconds; timer -server $0 -refnum auto_reconnect.$group_enc $auto_reconnect_delay auto_reconnect.handler $0; }; on #-channel_lost 100 * { if (serverctl(GET $0 STATE) == [CLOSING]) { @ :group = serverctl(GET $0 GROUP); @ :uuid = windowctl(GET $2 UUID); if (group == []) { @ :group_enc = encode($serverctl(GET $0 NAME)); } else { @ :group_enc = encode($group); }; push auto_reconnect[$group_enc][chans] $1; push auto_reconnect[$group_enc][keys] $key($1); push auto_reconnect[$group_enc][wins] $uuid; }; }; alias lsreconnects (void) { xecho -v -b Currently reconnecting to following groups:; fe ($timerctl(REFNUMS)) timer { if (timer =~ [auto_reconnect.*]) { @ :group = decode($after(1 . $timer)); xecho -v -b $group; }; }; }; alias rmreconnect (void) { @ :server = windowctl(GET $winnum() SERVER); @ :group = serverctl(GET $server GROUP); if (group == []) { @ :group_enc = encode($serverctl(GET $server NAME)); } else { @ :group_enc = encode($group); }; xecho -v -b Canceling reconnect to $decode($group_enc); timer -delete auto_reconnect.$group_enc; }; } else { fe ($timerctl(REFNUMS)) timer { if (timer =~ [auto_reconnect.*]) { timer -delete $timer; }; }; on #-server_state 100 -'% % ACTIVE'; on #-reconnect_required 100 -*; on #-channel_lost 100 -*; alais -lsreconnects; alias -rmreconnect; alias -auto_reconnect.handler; }; }; set auto_reconnect on;