home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 18 REXX
/
18-REXX.zip
/
RXNEWS1A.ZIP
/
rexxnews.cmd
< prev
next >
Wrap
OS/2 REXX Batch file
|
1993-04-19
|
31KB
|
1,046 lines
/*------------------------------------------------------------------
* RexxNews
*------------------------------------------------------------------
* 04-15-93 by Albert L. Crosby
*------------------------------------------------------------------
* Portions of this package are based on rnr.cmd :
*------------------------------------------------------------------
* 08-09-92 originally by Patrick J. Mueller
*------------------------------------------------------------------*/
settings.=""
settings.version="RexxNews v. 1.0a by Albert Crosby"
settings.varnames="newsrcname overlap headers displayatgroup rows cols ",
"groupname groupstat grouphighest groupnewsrcline ",
"newsrcdate newsrctime newgroupsatconnect etcdir ",
"rexxnewsdir newarticlesatgroup postingok newuser",
"server usexhdr"
settings.vartypes="A W B B W W ",
"A A W W ",
"W W B A",
"A B B B",
"A B"
settings.etcdir=value('etc',,'OS2ENVIRONMENT')
/*USER DEFINEABLE CONSTANTS *//********************************/
settings.newsrcname='newsrc' /* Name of newsrc file (within the etcdir) */
settings.newgroupsatconnect=1 /* Show newgroups at connect time */
settings.overlap=2 /* Overlap in paging */
settings.headers=1 /* Display headers, 1=Yes, 0=No */
settings.displayatgroup=1 /* Display first article on entering newsgroup */
settings.savenewsrcatexit=0 /* Save the newsrc in case of an error */
settings.newarticlesatgroup=1 /* Display new article subjects when moved to group */
/****************************//***********************************************/
trace off
signal on halt name shutdown
parse arg argserver .
call opening
/*------------------------------------------------------------------
* initialize system function package
*------------------------------------------------------------------*/
if RxFuncQuery("SysLoadFuncs") then
do
rc = RxFuncAdd("SysLoadFuncs","RexxUtil","SysLoadFuncs")
rc = SysLoadFuncs()
end
/*------------------------------------------------------------------
* initialize socket function package
*------------------------------------------------------------------*/
if RxFuncQuery("SockLoadFuncs") then
do
rc = RxFuncAdd("SockLoadFuncs","RxSock","SockLoadFuncs")
rc = SockLoadFuncs()
end
parse source . . name
settings.rexxnewsdir=filespec('drive',name)||filespec('path',name)
parse value SysTextScreenSize() with settings.rows settings.cols
call opening
call loadsettings
if argserver\="" then settings.server=argserver
if (settings.server = "") then
do
say "Expecting a news server name to be passed as a parameter or in the the"
say "configuration file."
exit 1
end
if settings.newsrcname="newsrc" then settings.newsrcname=settings.etcdir||'\'||settings.newsrcname
settings.newuser=\loadnewsrc()
say
say 'Connecting to server...'
/*------------------------------------------------------------------
* get address of server
*------------------------------------------------------------------*/
rc = SockGetHostByName(settings.server,"host.!")
if (rc = 0) then
do
say "Unable to resolve server name" settings.server
exit
end
server = host.!addr
/*------------------------------------------------------------------
* open socket
*------------------------------------------------------------------*/
sock = SockSocket("AF_INET","SOCK_STREAM",0)
if (sock = -1) then
do
say "Error opening socket:" errno
exit
end
/*------------------------------------------------------------------
* connect socket
*------------------------------------------------------------------*/
server.!family = "AF_INET"
server.!port = 119
server.!addr = server
trc = SockConnect(sock,"server.!")
if (trc = -1) then
Error(sock,rc,"Error connecting to newsserver :" errno)
trc = GetResponse(sock)
do i = 1 to line.0
say line.i
end
parse var line.1 code .
if code=200 then
settings.postingok=1
else settings.postingok=0
if code\=200 & code\=201 then
do
settings.savenewsrcatexit=0
signal shutdown
end
trc = SendMessage(sock,"xhdr")
trc = GetResponse(sock)
parse var line.1 code .
if code=501 then settings.usexhdr=1
else settings.usexhdr=0
trc = SendMessage(sock,"MODE READER")
trc = GetResponse(sock)
parse var line.1 code .
if code\=500 then say "Server supports the INN extensions."
say
if settings.newuser then call help 'intro'
else /* Don't show new users all of the groups that exist (unless they ask)... */
if settings.newgroupsatconnect then
do
call newgroups
say
end
rc = Interact(sock)
/*------------------------------------------------------------------
* quittin' time!
*------------------------------------------------------------------*/
Shutdown:
if settings.savenewsrcatexit\=0 then call fileout 'newsrc.',settings.newsrcname, 1
trc = SendMessage(sock,"quit")
trc = SockSoclose(sock)
exit
/*------------------------------------------------------------------
* get command and execute in a loop
*------------------------------------------------------------------*/
Interact: procedure expose !. settings. newsrc.
sock = arg(1)
/*------------------------------------------------------------------
* commands is the commands currently implemented in rnr.cmd
*------------------------------------------------------------------*/
rawcommands = "STAT BODY HEAD NEWNEWS LIST RAW"
group=""
first=0
last=0
current=0
remain=0
articleavailable=0
do forever
commandline=prompt()
parse var commandline command args
if commandline=="" then iterate
if abbrev("QUIT",translate(command)) then
do
settings.savenewsrcatexit=1
leave
end
if ("EXIT"==translate(command)) then
do
call charout ,"Are you sure (newsrc will not be updated!)? "
if translate(SysGetKey("Echo"))="Y" then
do
settings.savenewsrcatexit=0
leave
end
say
iterate
end
if ("?" == command) | abbrev("HELP",translate(command)) then
do
rc = Help(args)
iterate
end
if ("SET" == translate(command)) then
do
call set args
iterate
end
if ("SHOW" == translate(command)) then
do
call display 'newsrc.',1
iterate
end
if abbrev("DETAILS",translate(command),2) then
do
call details
iterate
end
if abbrev("OS2",translate(command)) then
do
args
iterate
end
if abbrev("TIME",translate(command)) then
do
say 'Current time is:' time() 'on' date()
say
iterate
end
if abbrev("GROUP",translate(command)) then
do
if args=="" then
do
say 'Expecting a group name.'
iterate
end
articleavailable=0
trc = SendMessage(sock,'group '||args)
trc = GetResponse(sock)
parse var line.1 code .
if code=411 then
do
say 'No active group named 'args'.'
group=settings.groupname
iterate
end
parse var line.1 code remain first last group .
if remain>0 then
do
settings.unread=1
current=checknewsrc(group)
if first>current then current=first
else
do
if current>=last then
do
say "No unread articles in "group
settings.unread=0
current=last
trc = SendMessage(sock,'stat '||current)
trc = GetResponse(sock)
iterate
end
else
do
trc = SendMessage(sock,'stat '||current)
trc = GetResponse(sock)
parse var line.1 code .
if code\=223 then current=first
else
do
trc = SendMessage(sock,'next')
trc = GetResponse(sock)
parse var line.1 code .
if code=223 then parse var line.1 . current .
end
end
end
if settings.newarticlesatgroup then call headers 'subject'
if settings.displayatgroup & settings.unread then current=article(current)
end
else
do
say "No articles in group "group
group=settings.groupname
end
iterate
end
if abbrev("NEXT",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
call next
iterate
end
if abbrev("LAST",translate(command)) | abbrev("BACK",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
call last
iterate
end
if abbrev("ARTICLE",translate(command)) | abbrev("DISPLAY",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
current=article(args)
iterate
end
if abbrev("NEWGROUPS",translate(command)) then
do
call newgroups args
iterate
end
if abbrev("AUTHORS",translate(command)) | abbrev("FROM",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
call headers 'from',args
iterate
end
if abbrev("SAVE",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
if \articleavailable then
do
say "No article available to be saved. Display an article first."
iterate
end
if args="" then
do
call charout , "Write article to file: "
parse pull args
if args="" then iterate
end
call fileout 'line.',args
iterate
end
if abbrev("SUBJECTS",translate(command)) then
do
if group="" then
do
say "You must select a group first."
iterate
end
call headers 'subject',args
iterate
end
if abbrev("SEARCH",translate(command)) then
do
call search(args)
iterate
end
if wordpos(translate(command),rawcommands)=0 then
do
say 'Unknown or unimplemented command: 'command
iterate
end
if "RAW"==translate(command) then command=args
articleavailable=0
trc = SendMessage(sock,commandline)
trc = GetResponse(sock)
call display 'line.',1
end
return ""
/*------------------------------------------------------------------
* display
*------------------------------------------------------------------*/
Display:
parse arg list, n, string, initial, keylist
if list="" then return ""
if n="" then n=2
if initial="" then initial=0
_r=initial+1
_cls=0;
say
interpret "do _i = n to "list"0+1;",
"if pos(d2c(12),"list"_i,1)\=0 then do; _cls=1;_r=_r+((settings.rows-settings.overlap)-_r//(settings.rows-settings.overlap));end;",
"if _r//(settings.rows-settings.overlap)=0 | _i>"list"0 then",
"do;",
'call charout ,"---MORE'string'---";',
"if _i>"list"0 then call charout ,'<END>';",
'key=SysGetKey('NOECHO');',
'call charout ,d2c(13)||copies(" ",settings.cols-1)||d2c(13);',
'if "Q"==translate(key) then return "";',
'if d2c(13)==key then _r=_r-1;',
'if "U"==translate(key) & _i>2*(settings.rows-settings.overlap) then _i=_i-2*(settings.rows-settings.overlap);',
'if "T"==translate(key) then do;call SysCls;_i=n;_r=1;end;',
'if pos(translate(key),translate(keylist))\=0 then return translate(key);',
'if _cls then do; Call SysCls; _cls=0; end;',
'if length('list'_i)>settings.cols',
'then _r=_r+(length('list'_i)%settings.cols);',
'end;',
"if _i<="list"0 then say "list"_i;",
"_r=_r+1;",
"end"
return ""
/* An Alternate display function that limits displayed lines with a 'needle' */
DisplayN:
parse arg list, n, string, needle
if list="" then return
if n="" then n=2
_r=1
say
interpret "do _i = n to "list"0+1;",
"if _r//(settings.rows-settings.overlap)=0 | _i>"list"0 then",
"do;",
'call charout ,"---MORE'string'---";',
"if _i>"list"0 then call charout ,'<END>';",
'key=SysGetKey('NOECHO');',
'call charout ,d2c(13)||copies(" ",settings.cols-1)||d2c(13);',
'if "Q"==translate(key) then return;',
'if d2c(13)==key then _r=_r-1;',
'if "U"==translate(key) & _i>2*(settings.rows-settings.overlap) then _i=_i-2*(settings.rows-settings.overlap);',
'if "T"==translate(key) then do;call SysCls;_i=n;_r=1;end;',
'if length('list'_i)>settings.cols',
'then _r=_r+(length('list'_i)%settings.cols);',
'end;',
"if _i=1 | pos(translate(needle),translate("list"_i))\=0 then",
"do;",
"if _i<="list"0 then say "list"_i;",
"_r=_r+1;",
"end;",
"end"
return ""
/*------------------------------------------------------------------
* help
*------------------------------------------------------------------*/
Help: procedure expose settings.
arg topic
if topic="" then topic="general"
if "TOPICS"==translate(topic) then
do
topics.=""
call SysFileTree settings.rexxnewsdir||'*.rxn','topics','FO'
do i=topics.0 to 1 by -1
n=i+2
topics.n=filespec('name',topics.i)
topics.n=left(topics.n,pos('.',topics.n)-1)
end
n=topics.0+3
topics.n='topics'
topics.0=n
topics.1=settings.version
topics.2="Help is available for the following topics:"
call SysCls
call Display 'topics.',1,' (RexxNews Help Topics)'
return 1
end
if filein('help.',settings.rexxnewsdir||topic||'.rxn')=0 then
do
say "No help available for '"topic"'."
say "Type HELP for general information or HELP INTRO for an introduction to RexxNews"
return 0
end
call SysCls
call Display 'help.',1,' ('||topic||' help)'
return 1
/*------------------------------------------------------------------
* get a response from the server
*------------------------------------------------------------------*/
GetResponse: procedure expose !. line. settings.
sock = arg(1)
moreids = "100 215 220 221 222 230 231"
progress="\|/-"
line.0 = 1
line.1 = GetResponseLine(sock)
parse var line.1 rid .
if (wordpos(rid,moreids) = 0) then
return ""
o=0
do forever
call charout , substr(progress,1+o//length(progress),1)||d2c(13)
o = line.0 + 1
line.o = GetResponseLine(sock)
if (line.o = ".") then
do
call charout , " "||d2c(13)
return ""
end
line.0 = o
end
call charout " "||d2c(13)
return ""
/*------------------------------------------------------------------
* get a line from the server
*------------------------------------------------------------------*/
GetResponseLine: procedure expose !.
sock = arg(1)
crlf = d2c(13) || d2c(10)
if (symbol('!.buff') = "LIT") then
!.buff = ""
do while (pos(crlf,!.buff) = 0)
rc = SockRecv(sock,"data",8000)
!.buff = !.buff || data
end
p = pos(crlf,!.buff)
line = substr(!.buff,1,p-1)
!.buff = substr(!.buff,p+2)
return line
/*------------------------------------------------------------------
* send a string to the server
*------------------------------------------------------------------*/
SendMessage: procedure expose !.
sock = arg(1)
data = arg(2) || d2c(13) || d2c(10)
len = length(data)
do while (len > 0)
i = SockSend(sock,data);
if (errno <> 0) then
Error(-1,rc,"Error sending data to server.")
if (i <= 0) then
Error(sock,100,"Server closed the connection.")
data = substr(data,len+1)
len = length(data)
end
return 0
/*------------------------------------------------------------------
* halting ...
*------------------------------------------------------------------*/
Halting:
Error(sock,1,"error on line" sigl)
/*------------------------------------------------------------------
* exit with a message and return code
*------------------------------------------------------------------*/
Error: procedure
sock = arg(1)
retc = arg(2)
msg = arg(3)
if (sock <> -1) then
rc = SockSoClose(sock)
say msg
exit retc
opening:
/*------------------------------------------------------------------
* initialize system function package
*------------------------------------------------------------------*/
if RxFuncQuery("SysLoadFuncs") then
do
rc = RxFuncAdd("SysLoadFuncs","RexxUtil","SysLoadFuncs")
rc = SysLoadFuncs()
end
/*------------------------------------------------------------------
* initialize socket function package
*------------------------------------------------------------------*/
if RxFuncQuery("SockLoadFuncs") then
do
rc = RxFuncAdd("SockLoadFuncs","RxSock","SockLoadFuncs")
rc = SockLoadFuncs()
end
call SysCls
say settings.version
say
say "A NNTP NewsReader Client in REXX and rxSock for OS/2 2.x"
say
return
filein:
arg stem, filename
if arg()<2 then return 0
if stem="" | filename="" then
do
say "Error reading file "filename" into stem "stem
return 0
end
_i=0
if stream(filename,'c','OPEN READ')=='READY:' then
do
interpret 'do while stream(filename,"S")="READY";',
"_i=_i+1;",
stem"_i=linein(filename);",
'end;',
stem"0=_i"
end
call stream filename,'c','CLOSE'
interpret '_n='stem'0;if 'stem'_n="" then do;'stem'0=_n-1;_i=_n;end'
return _i
loadsettings: procedure expose settings.
if filein('set.',settings.etcdir||'\REXXNEWS.CFG')=0 then return 0
do i=1 to set.0
if set.i\="" & left(set.i,1)\=';' then call set(set.i)
end
return 1
loadnewsrc:
say 'Processing newsrc file...'
say settings.newsrcname
if stream(settings.newsrcname,'c','query exists')\="" then
do
datetime=stream(settings.newsrcname ,'c','query datetime')
parse var datetime mo'-'dd'-'yy hh':'mm':'ss
settings.newsrcdate=yy||mo||dd
settings.newsrctime=hh||mm||ss
say filein('newsrc.',settings.newsrcname) 'lines processed.'
return 1
end
else
do
settings.newsrcdate=""
settings.newsrctime=""
newsrc.0=0
return 0
end
updatenewsrc: procedure expose settings. newsrc.
parse arg group, article
if translate(group)\=translate(settings.groupname) then
do
say "Error updating newsrc: wrong group."
return
end
i=settings.groupnewsrcline
if article<=settings.grouphighest then return
newsrc.i=group||settings.groupstat||' 1-'||article
return
prompt: procedure expose sock group first last current settings.
say
if group\="" then
do
trc = SendMessage(sock,'stat')
trc = GetResponse(sock)
parse var line.1 code current msgid .
say group "("first"-"last") --- Current article "current" ["last-current" remaining]"
end
if settings.newuser then
do
say "Enter HELP INTRO to review the introduction to RexxNews."
if group="" then
say "Enter Group <groupname> to move to a group."
else say "Enter Display to see the current article or Next for the next article."
end
say "Enter RexxNews command (or help or quit)"
parse pull commandline
return commandline
fileout:
parse arg stem, filename, compress
if \datatype(compress,'B') | compress>1 then compress=0
if stem="" then return 0;
interpret "if "stem"0 = 0 then return 0"
call stream filename,'c','open write'
call lineout filename,,1
if stream(filename,'s')\="READY" then
do
say "Error writing to file "filename": "_state
return 0
end
_n=0
interpret 'do _i=1 to 'stem'0;',
'if (\compress) | strip('stem'_i)\="" then',
'do;',
'_n=_n+1;',
'call lineout filename, 'stem'_i;',
'end;',
'end'
say _n' line(s) written to 'filename
return _i
checknewsrc: procedure expose settings. newsrc.
parse arg group
if group="" then return 0
do i= 1 to newsrc.0
parse var newsrc.i name pointer
if name="" then iterate
stat=right(name,1)
if pos(stat,':!')=0 then
do
stat=" "
end
else
do
name=left(name,length(name)-1)
end
if translate(name)=translate(group) then
do
settings.groupstat=stat
settings.groupname=name
settings.groupnewsrcline=i
n=translate(pointer," ","-,")
n=word(n,words(n))
if \datatype(n,'w') then n=0
settings.grouphighest=n
return n
end
end
settings.groupnewsrcline=i
settings.grouphighest=0
settings.groupstat=' '
settings.groupname=group
newsrc.0=i
newsrc.i=group||' 0'
return 0
newgroups: procedure expose sock settings.
arg _datetime
parse var _datetime _date _time
if _time="" then _time="000000"
if _date="" then
do
_date=settings.newsrcdate
_time=settings.newsrctime
end
trc = SendMessage(sock,'newgroups'||' '||_date||' '||_time)
trc = GetResponse(sock)
parse var line.1 code number id .
if line.0=1 then
do
say 'No new groups since '_date _time'.'
return
end
line.1='New groups since '_date _time'.'
call SysCLS
call Display 'line.',1
return
xhdr: procedure expose sock current settings.
parse arg tag, article
if \datatype(article,'W') then article=""
if tag="" then tag="subject"
if settings.usexhdr then
do
trc = SendMessage(sock,'xhdr '||tag||' '||article)
trc = GetResponse(sock)
value=""
if line.0>1 then parse var line.2 article value
return value
end
trc = SendMessage(sock,'head '||article)
trc = GetResponse(sock)
parse var line.1 code .
if code\=221 then return ""
do i=2 to line.0
parse var line.i ln":"value
if translate(tag)=translate(ln) then leave
value=""
end
trc = SendMessage(sock,'stat '||current)
trc = GetResponse(sock)
return value
article: procedure expose settings. newsrc. sock articleavailable line. current first last
arg num
if \articleavailable | num\=current then
do
if settings.headers=1
then command='ARTICLE'
else command='BODY'
trc = SendMessage(sock,command||' '||num)
trc = GetResponse(sock)
parse var line.1 code number id .
if code\=220 then
do
say "Error retrieving article: Code "code
articleavailable=0
return 0
end
articleavailable=1
call updatenewsrc settings.groupname, number
end
else number=current
call SysCLS
current=number
key=Display('line.', ,' (line "_i-1" of "'line.0'" article "current" of "last")', ,'NL')
if key='N' then return next()
if key='L' then return last()
return number
headers: procedure expose settings. sock first last current
parse arg tag, range, needle
if range="" then range=current||'-'||last
if range="*" then range=first||'-'||last
if tag="" then tag='subject'
if (translate(tag)\="GROUPS") then
do
say "Retreiving the "tag" field for article(s) "range"..."
msg=tag||' fields for article(s) 'range
if needle\="" then msg=msg||' containing 'needle':'
else msg=msg||':'
if settings.usexhdr then
do
trc = SendMessage(sock,'xhdr '||tag||' '||range)
trc = GetResponse(sock)
parse var line.1 code number id .
end
else
do
parse var range begin'-'end
line.=""
n=2
do i=begin to end
line.n=i||' '||xhdr(tag,i)
end
code=221
end
end
else
do
say "Retrieving a list of all groups... This may take a few moments."
msg='List of groups containing '||needle||' in their names:'
trc = SendMessage(sock,'list')
trc = GetResponse(sock)
parse var line.1 code number id .
end
if code\=221 & code\=215 then
do
say "Error retrieving list: Code "code
return 0
end
if line.0=1 then
do
say "No articles in "range"."
return
end
line.1=msg
call SysCLS
if needle="" then call Display 'line.',1
else call DisplayN 'line.',1,,needle
return number
set: procedure expose settings.
parse arg command
if command\="" then
do
parse var command variable value
i=wordpos(translate(variable),translate(settings.varnames))
if i=0 then
do
say "Unknown variable: "variable
return 0
end
else
do
if word(settings.vartypes,i)="A" | datatype(value,word(settings.vartypes,i)) then
interpret "settings."variable"=value"
else say "Improper argument type for "variable"."
return 1
end
end
set.=""
set.0=words(settings.varnames)+5
set.1=settings.version "Settings"
set.3=left("Variable",25)||left("Value",25)||"Type"
set.4=left("========",25)||left("=====",25)||"===="
do i=1 to words(settings.varnames)
n=i+4
interpret "set.n=left(word(settings.varnames,i),25)||left(settings."word(settings.varnames,i)",25)||word(settings.vartypes,i)"
end
call Display 'set.',1,' (RexxNews SET values)'
return
details:
say
say "Current group: "group
say "Available articles: "remain
say "First article: "first
say "Last article: "last
say "Current article: "current
if group\="" then
do
say "Article can be saved:" articleavailable
say "Subject: "xhdr(subject)
say "From: "xhdr(from)
say "Lines: "xhdr(lines)
_i=settings.groupnewsrcline
say "newsrc line #"_i
say "newsrc line: "newsrc._i
end
return
search: procedure expose settings. sock first last current
arg args
if args="" then return 0
!field=""
parse var args field rest 1 "RANGE " range . 1 "FOR " needle
if field='RANGE' | field='FOR' | (needle="" & rest="") then !field='SUBJECT'
if (needle="" & rest="") then needle=field
if range="" then range=current||'-'||last
else if range="*" then range=first||'-'||last
if !field\="" then field=!field
if settings.groupname="" & "GROUPS"\=translate(field) then
do
say "You must select a group first."
return 0
end
if needle="" then
do
say "The syntax of the SEARCH command is:"
say " SEarch [<field>] [RANGE <range>] [FOR] <string>"
say " See HELP SEARCH for more information."
return 0
end
call headers field, range, needle
return 1
next: procedure expose settings. newsrc. sock articleavailable current first last line.
if current=last then
do
say "No more articles."
return current
end
trc = SendMessage(sock,'next')
trc = GetResponse(sock)
articleavailable=0
current=article()
return current
last: procedure expose settings. newsrc. sock articleavailable current first last line.
if current=first then
do
say "No previous article."
return current
end
trc = SendMessage(sock,'LAST')
trc = GetResponse(sock)
articleavailable=0
current= article()
return current
/*
The 'Simplified' newsrc file:
The date of the file is used to find 'new' newsgroups.
newsgroup.name[:|!] [current_article]
Where: ':' indicates a 'subscribed' group
'!' indicates an 'unsubscribed' group
The 'current_article' is the number of the last article read in the group.
NO USE OF THE SUBSCRIBED/UNSUBSCRIBED STATUS IS MADE AT PRESENT!!
Note: This reader *is* capable of using a Unix .newsrc file.
Unimplemented features are ignored.
*/
/* Comments:
The first release will not include a post command.
The second release may add the subscribed concept by adding a
nextgroup command. The nextgroup command will move to the next
subscribed group in the .newsrc that has articles available to read.
*/