Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5ec8fb158 | ||
|
|
aa84c33e08 | ||
|
|
a658151d64 | ||
|
|
31b68d9a1f | ||
|
|
4653e5cea5 | ||
|
|
b13e6114ba | ||
|
|
3abe2517c1 | ||
|
|
bec03ff251 | ||
|
|
841ad4f858 | ||
|
|
185966f036 | ||
|
|
c15052a9da | ||
|
|
a182b4012c | ||
|
|
181322937b | ||
|
|
79bf56de47 | ||
|
|
895976e0d9 | ||
|
|
f13660869f | ||
|
|
6c627e865f |
@@ -1,3 +1,37 @@
|
|||||||
# Connex
|
# Connex
|
||||||
|
|
||||||
A TUI nex browser in perl using Curses
|
A TUI (terminal UI) nex browser in perl using Curses. See more about the smolnet *Nightfall Express* (nex://) protocol here: https://nightfall.city/nex/
|
||||||
|
|
||||||
|
Currently not in one-touch install shape, but you can build it and use it with *cpanm* to get the required libraries.
|
||||||
|
For all the frippery I added trying to package things the right way for perl, the single script file is just in *scripts/connex.pl* you should be able to run and navigate wherever you'd like in nex-space. ctl-x gets the menu, ">" key lets you choose a link on the page that the browser attaches "[>#]" to mark. "<" navigates back, ctl-q to quit. There is a help dialog accessible from the menu for this meager help:
|
||||||
|
|
||||||
|
```
|
||||||
|
Browsing:
|
||||||
|
Press '>' key to select a '[>#] link by #
|
||||||
|
Press '<' key to go back to previous page
|
||||||
|
Press 'ctl-Y' to see viewed page history
|
||||||
|
Press 'ctl-P' to see list of links on current page
|
||||||
|
|
||||||
|
Scrolling:
|
||||||
|
Arrow keys to scroll document
|
||||||
|
Press 'PageDown' to scroll down by page
|
||||||
|
Press 'PageUp' to scroll up by page
|
||||||
|
|
||||||
|
Search:
|
||||||
|
'less' type search function supported:
|
||||||
|
Press '/' to start a forward search (type search term, hit <enter>)
|
||||||
|
search for the next occurance using the 'n' key or the previous
|
||||||
|
occurance using the 'N' key.
|
||||||
|
Press '?' to start a reverse search (type search term, hit <enter>)
|
||||||
|
|
||||||
|
More Program Features:
|
||||||
|
Press ctl-x for menu
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The script needs the following Perl packages:
|
||||||
|
* FindBin
|
||||||
|
* Curses::UI
|
||||||
|
* Net::Telnet
|
||||||
|
* URI
|
||||||
|
* URI::Split
|
||||||
+131
-47
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/perl -w
|
#!/usr/bin/perl -w
|
||||||
# connex.pl Nightfall Express (nex://) browser
|
# connex.pl Nightfall Express (nex://) browser
|
||||||
# By Pete Dussin, peteyboy@sdf.org 3/2024
|
# By Pete Dussin, peteyboy@sdf.org 3/2024
|
||||||
|
# updated 6/2026
|
||||||
# This program uses telnet instead of nc to make nex requests, because I couldn't get the Perl nc module to work right.
|
# This program uses telnet instead of nc to make nex requests, because I couldn't get the Perl nc module to work right.
|
||||||
|
|
||||||
#TODO: Fill out help Dialog, mention vi navigatio and search
|
|
||||||
#TODO: Fill out About Dialog
|
#TODO: Fill out About Dialog
|
||||||
#TODO: Make status dialog actually useful
|
#TODO: Make status dialog actually useful
|
||||||
#TODO: bookmarks, maybe a quick mark-and-hold toggle to jump between two pages?
|
#TODO: bookmarks, maybe a quick mark-and-hold toggle to jump between two pages?
|
||||||
@@ -18,6 +18,7 @@ use Curses::UI;
|
|||||||
use Net::Telnet;
|
use Net::Telnet;
|
||||||
use URI::Split qw(uri_split uri_join);
|
use URI::Split qw(uri_split uri_join);
|
||||||
use URI ();
|
use URI ();
|
||||||
|
use Scalar::Util 'looks_like_number';
|
||||||
use Term::ReadKey; #for fatpacker?
|
use Term::ReadKey; #for fatpacker?
|
||||||
|
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ if (defined $full_url){
|
|||||||
die "Need a $SCHEME_NEX url, or start without supplying URL argument.\n";
|
die "Need a $SCHEME_NEX url, or start without supplying URL argument.\n";
|
||||||
}
|
}
|
||||||
$full_url= $uri->as_string;
|
$full_url= $uri->as_string;
|
||||||
$HOME_URL = $full_url; #TODO make sure you want to do this, mostly go home and other defaults will go here instead o nightfall.city
|
$HOME_URL = $full_url; #TODO make sure you want to do this, mostly go home and other defaults will go here instead of nightfall.city
|
||||||
}else{
|
}else{
|
||||||
$full_url = $HOME_URL;
|
$full_url = $HOME_URL;
|
||||||
}
|
}
|
||||||
@@ -84,19 +85,19 @@ my $connect = new Net::Telnet (Timeout => 10,
|
|||||||
my @menu = (
|
my @menu = (
|
||||||
{ -label => 'File',
|
{ -label => 'File',
|
||||||
-submenu => [
|
-submenu => [
|
||||||
{ -label => 'Choose Link >', -value => \&goto_link_dialog },
|
{ -label => 'Choose Link >', -value => \&goto_link_dialog },
|
||||||
{ -label => 'Back ^B/<', -value => \&goto_back },
|
{ -label => 'Back ^B/<', -value => \&goto_back },
|
||||||
{ -label => 'Go to Link ^G', -value => \&navigate_link_dialog },
|
{ -label => 'Go to Link ^G', -value => \&navigate_link_dialog },
|
||||||
{ -label => 'Go Home ^M', -value => \&goto_home },
|
{ -label => 'Go Home ^M', -value => \&goto_home },
|
||||||
{ -label => 'Page Links ^P', -value => \&page_links_dialog },
|
{ -label => 'Page Links ^P', -value => \&page_links_dialog },
|
||||||
{ -label => 'History ^Y', -value => \&history_status_dialog },
|
{ -label => 'Browser History ^Y', -value => \&history_status_dialog },
|
||||||
{ -label => 'Exit ^Q', -value => \&exit_dialog }
|
{ -label => 'Exit ^Q', -value => \&exit_dialog }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ -label => 'Help',
|
{ -label => 'Help',
|
||||||
-submenu => [
|
-submenu => [
|
||||||
{ -label => 'Help ^H', -value => \&help_dialog },
|
{ -label => 'Help ^H', -value => \&help_dialog },
|
||||||
{ -label => 'About ', -value => \&about_dialog },
|
{ -label => 'About ', -value => \&about_dialog },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ my @menu = (
|
|||||||
sub exit_dialog()
|
sub exit_dialog()
|
||||||
{
|
{
|
||||||
my $return = $cui->dialog(
|
my $return = $cui->dialog(
|
||||||
-title => "Quit Connex?",
|
-title => "Quit Connex",
|
||||||
-message => "Are you sure?",
|
-message => "Are you sure?",
|
||||||
-buttons => ['yes', 'no'],
|
-buttons => ['yes', 'no'],
|
||||||
|
|
||||||
@@ -132,6 +133,9 @@ sub links_dialog()
|
|||||||
sub unsupported_dialog
|
sub unsupported_dialog
|
||||||
{
|
{
|
||||||
my $scheme = shift;
|
my $scheme = shift;
|
||||||
|
if (!defined $scheme){
|
||||||
|
$scheme="unknown";
|
||||||
|
}
|
||||||
my $return = $cui->dialog(
|
my $return = $cui->dialog(
|
||||||
-message => "$scheme protocol not supported.",
|
-message => "$scheme protocol not supported.",
|
||||||
-title => "Bad Navigation",
|
-title => "Bad Navigation",
|
||||||
@@ -159,16 +163,19 @@ sub goto_link_dialog()
|
|||||||
);
|
);
|
||||||
#if not canceled, or too big or too small, goto link
|
#if not canceled, or too big or too small, goto link
|
||||||
if($return){
|
if($return){
|
||||||
my $linkcount = scalar @page_links;
|
if(looks_like_number($return)){
|
||||||
if($return <= $linkcount && $return >0){
|
my $linkcount = scalar @page_links;
|
||||||
update_status($S_REQUESTING);
|
if($return <= $linkcount && $return >0){
|
||||||
goto_link($return);
|
update_status($S_REQUESTING);
|
||||||
}else{
|
goto_link($return);
|
||||||
#$browser->focus();
|
}else{
|
||||||
my $return1 = $cui->status("there is no link # " . $return);
|
my $return1 = $cui->status("there is no link # " . $return);
|
||||||
|
}
|
||||||
|
}else{ #they are using it to navigate to a different site or for some other reason entered text
|
||||||
|
my $return2 = $cui->status("You did not enter a link #. To Go to a different site, use 'Go' menu or type ctl-g");
|
||||||
}
|
}
|
||||||
#do nothing on cancel
|
|
||||||
}
|
}
|
||||||
|
#do nothing on cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -182,7 +189,7 @@ sub goto_back()
|
|||||||
#if($fetched) {
|
#if($fetched) {
|
||||||
$full_url =$fetched;
|
$full_url =$fetched;
|
||||||
update_status($S_REQUESTING);
|
update_status($S_REQUESTING);
|
||||||
load($full_url,0); #don't move back to top
|
load($full_url,1); #move back to top
|
||||||
history_status_dialog();
|
history_status_dialog();
|
||||||
#}
|
#}
|
||||||
|
|
||||||
@@ -198,7 +205,7 @@ sub goto_home()
|
|||||||
$full_url=$HOME_URL;
|
$full_url=$HOME_URL;
|
||||||
}
|
}
|
||||||
update_status($S_REQUESTING);
|
update_status($S_REQUESTING);
|
||||||
load($full_url,0); #don't move back to top
|
load($full_url,1); #move back to top
|
||||||
history_status_dialog();
|
history_status_dialog();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -211,7 +218,7 @@ sub goto_link{
|
|||||||
#my $element=$linknum -1; #offset for array element
|
#my $element=$linknum -1; #offset for array element
|
||||||
#if ($element <= $#page_links){ #such a perlism, this is highest INDEX of array
|
#if ($element <= $#page_links){ #such a perlism, this is highest INDEX of array
|
||||||
|
|
||||||
if ($linknum <= $linkcount){ #such a perlism, this is highest INDEX of array
|
if ($linknum <= $linkcount){
|
||||||
navigate($page_links[$linknum-1]); #offset from count to index
|
navigate($page_links[$linknum-1]); #offset from count to index
|
||||||
}else{
|
}else{
|
||||||
#my $browser = $win1->getobj("browser");
|
#my $browser = $win1->getobj("browser");
|
||||||
@@ -231,6 +238,15 @@ sub navigate{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub browser_scroll_reset
|
||||||
|
{
|
||||||
|
my $browser = $win1->getobj("browser");
|
||||||
|
$browser->{-ypos} = 0;
|
||||||
|
#$browser->{-viewPos} =0; none of this works
|
||||||
|
$browser->draw();
|
||||||
|
$browser->focus();
|
||||||
|
}
|
||||||
|
|
||||||
sub update_status_bar
|
sub update_status_bar
|
||||||
{
|
{
|
||||||
my $status = shift;
|
my $status = shift;
|
||||||
@@ -291,12 +307,16 @@ sub history_status_dialog
|
|||||||
|
|
||||||
sub page_links_dialog
|
sub page_links_dialog
|
||||||
{
|
{
|
||||||
|
my $browser = $win1->getobj("browser");
|
||||||
|
$browser->focus();
|
||||||
|
#my $link_list = join("\n", @page_links);
|
||||||
if(@page_links){
|
if(@page_links){
|
||||||
my $browser = $win1->getobj("browser");
|
|
||||||
$browser->focus();
|
|
||||||
#my $link_list = join("\n", @page_links);
|
|
||||||
my $link_list = page_links_list();
|
my $link_list = page_links_list();
|
||||||
my $return = $cui->status("links on this page:\n$link_list");
|
my $return = $cui->status("links on this page:\n$link_list");
|
||||||
|
#}elsif (!is_path_index($browser->title())){ #if not an index, actually is this even important to the user?
|
||||||
|
# my $return = $cui->status("Not an index page.");
|
||||||
|
}else{ #there are no links in this index (should be rare, and index with no links?)
|
||||||
|
my $return = $cui->status("No links on this page.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +332,7 @@ sub page_links_list
|
|||||||
my $count=0;
|
my $count=0;
|
||||||
foreach $link (@page_links){
|
foreach $link (@page_links){
|
||||||
$count+=1;
|
$count+=1;
|
||||||
$link_list = $link_list . "[$count] $link\n";
|
$link_list = $link_list . "[>$count] $link\n";
|
||||||
}
|
}
|
||||||
return $link_list;
|
return $link_list;
|
||||||
}
|
}
|
||||||
@@ -330,12 +350,27 @@ sub help_dialog
|
|||||||
my $browser = $win1->getobj("browser");
|
my $browser = $win1->getobj("browser");
|
||||||
$browser->focus();
|
$browser->focus();
|
||||||
my $message = <<'END_MESSAGE';
|
my $message = <<'END_MESSAGE';
|
||||||
Navigation:
|
Browsing:
|
||||||
Press '>' key to select a '[>#]link by #
|
Press '>' key to select a '[>#] link by #
|
||||||
Press '<' key to go back to previous page
|
Press '<' key to go back to previous page
|
||||||
|
Press 'ctl-Y' to see viewed page history
|
||||||
|
Press 'ctl-P' to see list of links on current page
|
||||||
|
|
||||||
Program Features:
|
Scrolling:
|
||||||
|
Arrow keys to scroll document
|
||||||
|
Press 'PageDown' to scroll down by page
|
||||||
|
Press 'PageUp' to scroll up by page
|
||||||
|
|
||||||
|
Search:
|
||||||
|
'less' type search function supported:
|
||||||
|
Press '/' to start a forward search (type search term, hit <enter>)
|
||||||
|
search for the next occurance using the 'n' key or the previous
|
||||||
|
occurance using the 'N' key.
|
||||||
|
Press '?' to start a reverse search (type search term, hit <enter>)
|
||||||
|
|
||||||
|
More Program Features:
|
||||||
Press ctl-x for menu
|
Press ctl-x for menu
|
||||||
|
|
||||||
END_MESSAGE
|
END_MESSAGE
|
||||||
|
|
||||||
my $return = $cui->dialog(
|
my $return = $cui->dialog(
|
||||||
@@ -397,6 +432,7 @@ $statusbar = $win1->add("status", "TextViewer",
|
|||||||
|
|
||||||
#key bindings, should match menu items
|
#key bindings, should match menu items
|
||||||
$cui->set_binding(sub {$menu->focus()}, "\cX");
|
$cui->set_binding(sub {$menu->focus()}, "\cX");
|
||||||
|
$cui->set_binding( \&help_dialog , "\cH");
|
||||||
$cui->set_binding( \&exit_dialog , "\cQ");
|
$cui->set_binding( \&exit_dialog , "\cQ");
|
||||||
$cui->set_binding( \&navigate_link_dialog , "\cG");
|
$cui->set_binding( \&navigate_link_dialog , "\cG");
|
||||||
$cui->set_binding( \&goto_back , "\cB");
|
$cui->set_binding( \&goto_back , "\cB");
|
||||||
@@ -405,7 +441,6 @@ $cui->set_binding( \&goto_link_dialog , ">");
|
|||||||
$cui->set_binding( \&goto_home , "\cM");
|
$cui->set_binding( \&goto_home , "\cM");
|
||||||
$cui->set_binding( \&page_links_dialog , "\cP");
|
$cui->set_binding( \&page_links_dialog , "\cP");
|
||||||
$cui->set_binding( \&history_status_dialog , "\cY");
|
$cui->set_binding( \&history_status_dialog , "\cY");
|
||||||
$cui->set_binding( \&help_dialog , "\cH");
|
|
||||||
$cui->set_binding(sub {
|
$cui->set_binding(sub {
|
||||||
my $cui = shift;
|
my $cui = shift;
|
||||||
$cui->layout;
|
$cui->layout;
|
||||||
@@ -453,22 +488,59 @@ sub is_path_index{
|
|||||||
return($result);
|
return($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub add_page_link{
|
#sub add_page_link{
|
||||||
my $linkline = shift;
|
# my $linkline = shift;
|
||||||
my $base_url = shift;
|
# my $base_url = shift;
|
||||||
my $uri_object;
|
# my $uri_object;
|
||||||
#my $link= $linkline=~ s/^=>[ ]*(.*$)/$1/r;
|
#my $link= $linkline=~ s/^=>[ ]*(.*$)/$1/r;
|
||||||
local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
|
# local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
|
||||||
local $URI::ABS_REMOTE_LEADING_DOTS = 1;
|
# local $URI::ABS_REMOTE_LEADING_DOTS = 1;
|
||||||
$uri_object=URI->new_abs($linkline=~ /^=>[ ]+([^ ]*)/,$base_url);
|
# $uri_object=URI->new_abs($linkline=~ /^=>[ ]+([^ ]*)/,$base_url);
|
||||||
|
#
|
||||||
|
# push(@page_links, $uri_object->as_string);
|
||||||
|
# my $count = scalar @page_links; #scalar is size/count of links, but $#page_links is highest INDEX
|
||||||
|
# return $linkline =~ s/(^=>.*$)/$1 [\>$count\]/r;
|
||||||
|
|
||||||
push(@page_links, $uri_object->as_string);
|
#}
|
||||||
my $count = scalar @page_links; #scalar is size/count of links, but $#page_links is highest INDEX
|
|
||||||
return $linkline =~ s/(^=>.*$)/$1 [\>$count\]/r;
|
sub add_page_link{
|
||||||
|
my $linkline = shift;
|
||||||
|
my $base_url = shift;
|
||||||
|
my $uri_object;
|
||||||
|
my $uritext;
|
||||||
|
my $description;
|
||||||
|
|
||||||
|
local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
|
||||||
|
local $URI::ABS_REMOTE_LEADING_DOTS = 1;
|
||||||
|
#remember inside of braces, ^ means NOT, so any NOT SPACE letters are selected, so everything up to next space
|
||||||
|
if ($linkline=~ /^=> ([^ ]*)/){ #has to be something here or I think URI->new_abs has exception that crashes app
|
||||||
|
$uri_object=URI->new_abs($linkline=~ /^=> ([^ ]*)/,$base_url);
|
||||||
|
$uritext = ($linkline=~ s/^=> ([^ ]*).*/$1/r);
|
||||||
|
chomp($uritext);
|
||||||
|
push(@page_links, $uri_object->as_string);
|
||||||
|
my $count = scalar @page_links; #scalar is size/count of links, but $#page_links is highest INDEX
|
||||||
|
#below puts the [>1] at the end of the description.
|
||||||
|
#return $linkline =~ s/(^=>.*$)/$1 [\>$count\]/r; don't need to escape on the replace part of a substitution, I guess?
|
||||||
|
#below puts the [>1] at the beginning of line, replacing =>
|
||||||
|
#return $linkline =~ s/^=>/[>$count]/r;
|
||||||
|
|
||||||
|
#This reconstructs the uri so it can put the [>1] between the uri and the description, but still on the same
|
||||||
|
#line as the url, if there isn't a description (was unable to figure out how to do in one regex!)
|
||||||
|
if ($linkline =~ /=> .* [^\n]+/){
|
||||||
|
$description = ($linkline=~ s/=> [^ ]+ ([^\n]+)/$1/r);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$description= "";
|
||||||
|
}
|
||||||
|
return "=> " . $uritext . " [>$count] " . $description . "\n";
|
||||||
|
}else{
|
||||||
|
return $linkline;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#=== NEX REQUESTS AND RESPONSE PROCESSING ===
|
#=== NEX REQUESTS AND RESPONSE PROCESSING ===
|
||||||
sub load
|
sub load
|
||||||
{
|
{
|
||||||
@@ -489,12 +561,26 @@ sub load
|
|||||||
$connect->port($port);
|
$connect->port($port);
|
||||||
$ok= $connect->open($host);
|
$ok= $connect->open($host);
|
||||||
$ok= $connect->print($path);
|
$ok= $connect->print($path);
|
||||||
|
#TODO: What if it's not a text file? We need to check and have a download prompt and download if it's not text!
|
||||||
@lines =$connect->getlines(ErrMode=> 'return');
|
@lines =$connect->getlines(ErrMode=> 'return');
|
||||||
print $connect->eof();
|
print $connect->eof();
|
||||||
#TODO:handle non-existant request? Die is not pretty to do here
|
#TODO:handle non-existent request? Die is not pretty to do here
|
||||||
die unless $connect->eof();
|
die unless $connect->eof();
|
||||||
$connect->close();
|
$connect->close();
|
||||||
|
|
||||||
my $widget= $texteditor;
|
my $widget= $texteditor;
|
||||||
|
# check if we want to scroll to top
|
||||||
|
if ($top eq 1){
|
||||||
|
#For widget programmers: To control the scrollbar, the widget data -vscrolllen (the total length of the content of the widget) and -vscrollpos (the current position in the document) should be set.
|
||||||
|
# If Curses::UI::Widget::draw is called, the scrollbar will be drawn.
|
||||||
|
#browser_scroll_reset();
|
||||||
|
#my $browser = $win1->getobj("browser");
|
||||||
|
$widget->{-ypos} = 0;
|
||||||
|
#$browser->{-viewPos} =0; none of this works
|
||||||
|
#$browser->draw();
|
||||||
|
#$browser->focus();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#loop through nex response and load into text editor. Identify, mark and store link lines for index pages
|
#loop through nex response and load into text editor. Identify, mark and store link lines for index pages
|
||||||
my $page_contents =""; #this is the page contents to be dumped into the browser widget
|
my $page_contents =""; #this is the page contents to be dumped into the browser widget
|
||||||
@@ -503,8 +589,8 @@ sub load
|
|||||||
my $is_index= is_path_index($path);
|
my $is_index= is_path_index($path);
|
||||||
undef @page_links; #clear list
|
undef @page_links; #clear list
|
||||||
foreach $currentline (@lines){
|
foreach $currentline (@lines){
|
||||||
if ($currentline =~ m/^=>/){
|
if ($is_index){ #only do this for index pages
|
||||||
if ($is_index){
|
if ($currentline =~ m/^=> /){ #check for valid rocket link with space
|
||||||
$currentline = add_page_link($currentline, $url);
|
$currentline = add_page_link($currentline, $url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,9 +598,7 @@ sub load
|
|||||||
}
|
}
|
||||||
$widget->text($page_contents);
|
$widget->text($page_contents);
|
||||||
$widget->draw();
|
$widget->draw();
|
||||||
if ($top){
|
|
||||||
$widget->pos(0);
|
|
||||||
}
|
|
||||||
if ($page_contents eq ''){
|
if ($page_contents eq ''){
|
||||||
update_status($S_NORESPONSE);
|
update_status($S_NORESPONSE);
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
Reference in New Issue
Block a user