Contributor: GAYLE DAVIS {NEWSQWK.PAS} { Converts USENET files to QWK format .. You'll need PKZIP to use this. I use NXpress for my Newsgroup reader, in it saves it files with an extension of .MBX. If you newsreader saves in someother format, then change the extension default at the front of the program. Perhaps you newsreader has a SAVEAS feature that allows you to download all of the material and save it as a text file. If so, you could use it. Just save the files as SOMEFILE.MBX in the same DIR as this program, and it'll create the QWK file for you. Gayle Davis 05/28/96 } {$V-,S-,I-} {$M 16384,0,655360} { no need to leave memory for PKZIP !!! see the EXECUTE procedure below and find out how !!} USES Dos, Crt, Upper, RLine; { NOTE : Upper is in STRINGS.SWG RLINE is in TEXTFILE.SWG } CONST ControlHdr : ARRAY [1..11] OF STRING [30] = ( {1} 'SOURCEWARE ARCHIVAL GROUP', { change this to whatever you want ! } {2} 'Goshen', { ditto } {3} '875-8133', { ditto } {4} 'Gayle Davis', { ditto } {5} '99999,SWAG', { ditto } {6} '11-03-1993,04:41:37', { this will get updated automatically } {7} 'SWAG Genius', { whatever pleases you ! } {8} '', { QMAIL Menu name ??? } {9} '0', { allways ZERO ??? } {10} '0', { total number of messages in package } {11} '0'); { number of conferences-1 here } { next is 0 , then first conference } TYPE BlockArray = ARRAY [1..128] OF CHAR; CharArray = ARRAY [1..6] OF CHAR; { to read in chunks } ControlArray = ARRAY [1..100] OF STRING [40]; { set to 100 conferences !!} bsingle = array [0..4] of byte; MSGDATHdr = RECORD Status : CHAR; MSGNum : ARRAY [1..7] OF CHAR; Date : ARRAY [1..8] OF CHAR; Time : ARRAY [1..5] OF CHAR; UpTO : ARRAY [1..25] OF CHAR; UpFROM : ARRAY [1..25] OF CHAR; Subject : ARRAY [1..25] OF CHAR; PassWord : ARRAY [1..12] OF CHAR; ReferNum : ARRAY [1..8] OF CHAR; NumChunk : CharArray; Alive : BYTE; LeastSig : BYTE; MostSig : BYTE; Reserved : ARRAY [1..3] OF CHAR; END; MBXHeader = RECORD Xref : STRING[70]; Path : STRING; From : STRING[70]; Subject : STRING[70]; Date : STRING[40]; Lines : WORD; Status : CHAR; END; CONST PKZIP : PathStr = 'PKZIP.EXE'; QWKFile : PathStr = 'NEWS.QWK'; VAR MBXF : TEXT; QWKF : FILE; ControlF : TEXT; FOL : FileOfLinesPtr; FOLPos : LONGINT; SavePath, SwagPath, MBXFn, MsgFName : PATHSTR; TR : SearchRec; ConfNum, Number : WORD; { message number, conference number } MSGHdr : MSGDatHdr; ch : CHAR; count : INTEGER; chunks : INTEGER; ControlVal : ControlArray; ControlIdx : BYTE; ConfName, WStr : STRING; FUNCTION TrimL (InpStr : STRING) : STRING; ASSEMBLER; ASM PUSH DS LDS SI, InpStr XOR AX, AX LODSB XCHG AX, CX LES DI, @Result INC DI JCXZ @@2 MOV BL, ' ' CLD @@1 : LODSB CMP AL, BL LOOPE @@1 DEC SI INC CX REP MOVSB @@2 : XCHG AX, DI MOV DI, WORD PTR @Result SUB AX, DI DEC AX STOSB POP DS END; FUNCTION TrimR (InpStr : STRING) : STRING; VAR i : INTEGER; BEGIN i := LENGTH (InpStr); WHILE (i >= 1) AND (InpStr [i] = ' ') DO i := i - 1; TrimR := COPY (InpStr, 1, i) END; FUNCTION TrimB (InpStr : STRING) : STRING; BEGIN TrimB := TrimL (TrimR (InpStr) ); END; FUNCTION PadR (InpStr : STRING; FieldLen : BYTE) : STRING; {-Return a string right-padded to length len with ch} VAR o : STRING; SLen : BYTE ABSOLUTE InpStr; BEGIN IF LENGTH (InpStr) >= FieldLen THEN PadR := COPY (InpStr, 1, FieldLen) ELSE BEGIN o [0] := CHR (FieldLen); MOVE (InpStr [1], o [1], SLen); IF SLen < 255 THEN FILLCHAR (o [SUCC (SLen) ], FieldLen - SLen, #32); PadR := o; END; END; FUNCTION GoodNumber (S : STRING) : BOOLEAN; VAR Num : LONGINT; Code : WORD; BEGIN Num := 0; VAL (S, Num, Code); GoodNumber := ( (Code = 0) AND (Num > 0) AND (S > '') ); END; FUNCTION IntStr (Num : LONGINT; Width : BYTE; Zeros : BOOLEAN) : STRING; { Return a string value (width 'w')for the input integer ('n') } VAR Stg : STRING; BEGIN STR (Num : Width, Stg); IF Zeros THEN BEGIN FOR Num := 1 TO Width DO IF Stg [Num] = #32 THEN Stg [Num] := '0'; END ELSE Stg := TrimL (Stg); IntStr := Stg; END; FUNCTION GetStr (VAR InpStr : STRING; Delim : CHAR) : STRING; VAR i : INTEGER; BEGIN i := POS (Delim, InpStr); IF i = 0 THEN BEGIN GetStr := InpStr; InpStr := '' END ELSE BEGIN GetStr := COPY (InpStr, 1, i - 1); DELETE (InpStr, 1, i) END END; FUNCTION Str2LongInt (S : STRING; VAR I : LONGINT) : BOOLEAN; {-Convert a string to an integer, returning true if successful} VAR code : WORD; BEGIN VAL (S, I, code); IF code <> 0 THEN BEGIN i := 0; Str2LongInt := FALSE; END ELSE Str2LongInt := TRUE; END; FUNCTION GetNumber (VAR InpStr : STRING; Delim : CHAR) : LONGINT; VAR S, S1 : STRING; I : LONGINT; BEGIN I := 0; S1 := InpStr; S := GetStr (InpStr, Delim); IF NOT GoodNumber (S) THEN InpStr := S1 ELSE Str2LongInt (S, I); GetNumber := I; END; FUNCTION NameOnly (FileName : PathStr) : PathStr; { Strip any path information from a file specification } VAR Dir : DirStr; Name : NameStr; Ext : ExtStr; BEGIN FSplit (FileName, Dir, Name, Ext); NameOnly := Name; END {NameOnly}; FUNCTION SlashDate(AddCentury : BOOLEAN) : STRING; {10/08/88} VAR MonthName, dayname, yearname, dayofweekname : WORD; BEGIN GETDATE (yearname, MonthName, dayname, dayofweekname); IF AddCentury THEN SlashDate := IntStr (MonthName, 2, TRUE) + '/' + IntStr (dayname, 2, TRUE) + '/' + IntStr (yearname, 4, TRUE) ELSE SlashDate := IntStr (MonthName, 2, TRUE) + '/' + IntStr (dayname, 2, TRUE) + '/' + COPY (IntStr (yearname, 4, TRUE), 3, 2); END; FUNCTION PlainTime : STRING; {09:10:01} VAR Hr, Min, Sec, sec100 : WORD; BEGIN GETTIME (Hr, Min, Sec, sec100); PlainTime := IntStr (Hr, 2, TRUE) + ':' + IntStr (Min, 2, TRUE) + ':' + IntStr (Sec, 2, TRUE); END; FUNCTION EraseFile ( S : PathStr ) : BOOLEAN ; VAR F : FILE; BEGIN EraseFile := FALSE; ASSIGN (F, S); RESET (F); IF IORESULT <> 0 THEN EXIT; CLOSE (F); ERASE (F); EraseFile := (IORESULT = 0); END; PROCEDURE ReallocateMemory(P : POINTER); ASSEMBLER; ASM MOV AX, PrefixSeg MOV ES, AX MOV BX, WORD PTR P+2 CMP WORD PTR P,0 JE @OK INC BX @OK: SUB BX, AX MOV AH, 4Ah INT 21h JC @X LES DI, P MOV WORD PTR HeapEnd,DI MOV WORD PTR HeapEnd+2,ES @X: END; FUNCTION EXECUTE(Name : PathStr ; Tail : STRING) : WORD; ASSEMBLER; ASM {$IFDEF CPU386} DB 66h PUSH WORD PTR HeapEnd DB 66h PUSH WORD PTR Name DB 66h PUSH WORD PTR Tail DB 66h PUSH WORD PTR HeapPtr {$ELSE} PUSH WORD PTR HeapEnd+2 PUSH WORD PTR HeapEnd PUSH WORD PTR Name+2 PUSH WORD PTR Name PUSH WORD PTR Tail+2 PUSH WORD PTR Tail PUSH WORD PTR HeapPtr+2 PUSH WORD PTR HeapPtr {$ENDIF} CALL ReallocateMemory CALL SwapVectors CALL DOS.EXEC CALL SwapVectors CALL ReallocateMemory MOV AX, DosError OR AX, AX JNZ @OUT MOV AH, 4Dh INT 21h @OUT: END; PROCEDURE FindPKZip; VAR S : PathStr; BEGIN S := FSearch ('PKZIP.EXE', GetEnv ('PATH') ); IF S = '' THEN BEGIN WriteLn(#7,'You GOTTA have PKZIP somewhere on your PATH to do this !!'); HALT(1); END; PKZIP := FExpand (S); END; PROCEDURE CleanUp; { clean up after ourselves } BEGIN FINDFIRST ('*.NDX', $21, TR); WHILE DosError = 0 DO BEGIN EraseFile(TR.NAME); FINDNEXT (TR); END; EraseFile('MESSAGES.DAT'); EraseFile('CONTROL.DAT'); END; PROCEDURE CreateControlDat; VAR I : BYTE; BEGIN ControlHdr [ 6] := SlashDate(TRUE)+','+PlainTime; ControlHdr [10] := IntStr (Count, 5, FALSE); ControlHdr [11] := IntStr (PRED (ConfNum), 3, FALSE); ASSIGN (ControlF, 'CONTROL.DAT'); REWRITE (ControlF); FOR I := 1 TO 11 DO WRITELN (ControlF, ControlHdr [i]); FOR I := 1 TO ControlIdx DO WRITELN (ControlF, ControlVal [i]); CLOSE (ControlF); END; PROCEDURE CreateMessageDat; VAR I : BYTE; Buff : BlockArray; BEGIN FILLCHAR (ControlVal, SIZEOF (ControlVal), #0); FILLCHAR (Buff, SIZEOF (Buff), #32); FILLCHAR (MsgHdr, SIZEOF (MsgHdr), #32); ConfNum := 0; ControlIdx := 0; Number := 0; ASSIGN (QWKF, 'MESSAGES.DAT'); REWRITE (QWKF, SIZEOF (MsgHdr) ); WStr := 'NEWS TO QWK (c) 1996 GDSOFT'; FOR I := 1 TO LENGTH (WStr) DO Buff [i] := WSTR [i]; BLOCKWRITE (QwkF, Buff, 1); END; FUNCTION ArrayTOInteger (B : CharArray; Len : BYTE) : LONGINT; VAR I : BYTE; S : STRING; E : INTEGER; T : INTEGER; BEGIN S := ''; FOR I := 1 TO PRED (Len) DO IF B [i] <> #32 THEN S := S + B [i]; VAL (S, T, E); IF E = 0 THEN ArrayToInteger := T; END; PROCEDURE GetNewsGroupHeader(VAR NGH : MBXHeader); VAR Junk : STRING; BEGIN WHILE POS('STATUS:',UpCaseStr(FOL^.LastLine)) = 0 DO BEGIN FOL^.SeekLine(FOLPos); INC(FOLPos); IF POS('XREF:',UpCaseStr(FOL^.LastLine)) > 0 THEN NGH.XRef := TrimB(COPY(FOL^.LastLine,6,$FF)); IF POS('PATH:',UpCaseStr(FOL^.Lastline)) > 0 THEN NGH.Path := TrimB(COPY(FOL^.LastLine,6,$FF)); IF POS('FROM:',UpCaseStr(FOL^.Lastline)) > 0 THEN NGH.From := TrimB(COPY(FOL^.LastLine,6,$FF)); IF POS('SUBJECT:',UpCaseStr(FOL^.Lastline)) > 0 THEN NGH.Subject := Trimb(COPY(FOL^.LastLine,9,$FF)); IF POS('DATE:',UpCaseStr(FOL^.Lastline)) > 0 THEN NGH.Date := Trimb(COPY(FOL^.LastLine,6,$FF)); IF POS('LINES:',UpCaseStr(FOL^.Lastline)) > 0 THEN BEGIN Junk := GetStr(FOL^.LastLine,#32); NGH.Lines := GetNumber(FOL^.LastLine,#32); END; IF POS('STATUS:',UpCaseStr(FOL^.Lastline)) > 0 THEN NGH.STATUS := 'S'; END; END; PROCEDURE ReadMessage(HdrPos : LONGINT); VAR HDR : MsgDatHdr; Block : BlockArray; EndPos : LONGINT; Chunks : LONGINT; J,K : INTEGER; I,SFOL : LONGINT; NS : STRING; NGH : MBXHeader; PROCEDURE MoveDataToBlock (Start, Len : BYTE; S : STRING; VAR Block : BlockArray); VAR I, K : BYTE; BEGIN K := 0; FOR I := Start TO PRED (Start + Len) DO BEGIN INC (k); Block [i] := S [k]; END; END; PROCEDURE WriteHeader; BEGIN { write the header out } Seek(QwkF,HdrPos); FillChar(Block,SizeOf(Block),#32); MoveDataToBlock( 2, 7,PadR(IntStr(Number,7,FALSE),7),Block); { number } MoveDataToBlock( 9, 8,SlashDate(FALSE),Block); { date } MoveDataToBlock( 17, 5,PlainTime,Block); { Time } MoveDataToBlock( 22,25,PadR(ControlHdr[4],25),Block); { To } MoveDataToBlock( 47,25,PadR(NGH.FROM,25),Block); { From } MoveDataToBlock( 72,25,PadR(NGH.Subject,25),Block); { Subj } MoveDataToBlock( 97,20,PadR('IMPORT',20),Block); { Confname } MoveDataToBlock(117, 6,PadR(IntStr(Chunks,6,FALSE),6),Block); { Numpacs } MoveDataToBlock(124, 1,Chr(64),Block); BlockWrite(QwkF,Block,1); END; PROCEDURE WriteBlock; BEGIN BLOCKWRITE (QwkF, Block, 1); FILLCHAR (Block, SIZEOF (Block), #32); INC (chunks); { increment block count } k := 0; END; PROCEDURE ProcessLine; VAR c : BYTE; BEGIN FOR c := 1 TO LENGTH(FOL^.LastLine) DO BEGIN INC (k); { IF FOL^.LastLine [c] = #13 THEN BEGIN Block [k] := #227; INC (c); END ELSE Block [k] := FOL^.LastLine [c]; } Block[k] := FOL^.Lastline[c]; IF k = 128 THEN WriteBlock; END; { for } { write end of line } INC(k); Block[k] := #227; IF k=128 THEN WriteBlock; END; BEGIN SFOL := SUCC(FOLPos); { read the header block } GetNewsGroupHeader(NGH); { fill QWK Header with info } FILLCHAR (Block, SIZEOF (Block), #32); FILLCHAR(Hdr,SizeOF(Hdr),#0); { write the header out } chunks := 1; { number packs } INC(Number); { update message number } { write the header to our QWK file } WriteHeader; { write the blocks out } K := 0; FILLCHAR (Block, SIZEOF (Block), #32); FOR I := FOLPos TO FOLPos + NGH.Lines DO BEGIN FOL^.SeekLine(i); ProcessLine; END; J := I; { save the FOLPos for later } { write the original header out } FOL^.LastLine := ' '; ProcessLine; FOL^.LastLine := 'Original Header:'; ProcessLine; FOL^.LastLine := ' '; ProcessLine; FOR I := SFOL TO FOLPos DO BEGIN FOL^.Seekline(i); ProcessLine; END; IF k > 0 THEN WriteBlock; FOLPos := j; { update the position in the file } EndPos := FilePos(QwkF); { update the header } WriteHeader; SEEK(QwkF, EndPos); END; PROCEDURE ProcessUseNetFile (FN : PathStr); { this is the heart !! Read messages from MBX file and save in QWK file } VAR ndxF : File; b : bSingle; r : REAL; n : LONGINT; { converts TP real to Microsoft 4 bytes single .. GOOFY !!!! } procedure real_to_msb (preal : real; var b : bsingle); var r : array [0 .. 5] of byte absolute preal; begin b [3] := r [0]; move (r [3], b [0], 3); end; { procedure real_to_msb } BEGIN WriteLn('Process .. ',FN); { create the NDX file } ASSIGN (ndxF,IntStr(ConfNum,3,TRUE)+'.NDX'); REWRITE (ndxF,1); WHILE (FOLPos < FOL^.Totallines) DO BEGIN n := SUCC(FilePos(QwkF)); { ndx wants the RELATIVE position } r := N; { make a REAL } REAL_TO_MSB(r,b); { convert to MSB format } BLOCKWRITE(ndxF,B,SizeOf(B)); { store it } WriteLn('Process message .. ',IntStr(Number+1,5,FALSE)); ReadMessage(PRED(n)); INC(Count); END; CLOSE (NdxF); { update the CONTROL file array } INC (ControlIdx); ControlVal [ControlIdx] := IntStr (ConfNum, 3, TRUE); INC (ControlIdx); ControlVal [ControlIdx] := ConfName; INC (ConfNum); END; PROCEDURE GetConferenceName; VAR Junk : STRING; BEGIN WHILE POS('NEWSGROUPS:',UpCaseStr(FOL^.LastLine)) = 0 DO BEGIN FOL^.SeekLine(FOLPos); INC(FOLPos); END; Junk := GetStr(FOL^.LastLine,' '); ConfName := TrimB(FOL^.Lastline); FOLPos := 1; END; BEGIN ClrScr; IF ParamCount > 0 THEN MBXfn := FExpand(ParamStr(1)) ELSE MBXfn := '*.MBX'; EraseFile(QWKFile); { make sure we don't have one yet } FindPkZip; CreateMessageDat; Count := 0; { total messages in package } { process all the files that we find with the extension } FINDFIRST (MBXFn, $21, TR); WHILE DosError = 0 DO BEGIN NEW(FOL, Init(TR.Name, 1024)); FOLPos := 1; { current position in RLINE array } GetConferenceName; ProcessUseNetFile (TR.Name); DISPOSE (FOL, Done); FindNext(TR); END; CLOSE (QwkF); CreateControlDat; Execute(PKZIP,' -ex '+QWKFile+' *.NDX MESSAGES.DAT CONTROL.DAT'); CleanUp; END.