ITADN
RainRat/pyqwk
RainRat/pyqwk · 文件
文件最后提交记录最后更新时间
README.md

pyqwk

pyqwk converts message archives into modern, readable formats like HTML, Markdown, and SQLite. It supports many file types, including QWK, ZIP, TAR, JSON, CSV, mbox, EML, and more.

What are QWK and REP files?

QWK files started in the 1980s for Bulletin Board Systems (BBS). Users downloaded messages in a single "packet," read them offline, and then uploaded replies in a .REP packet.

Inside these packets, messages are organized into Conferences. A Conference is like a modern forum or channel dedicated to a specific topic.

Features

  • Support many formats: Import and export between QWK, JSON, JSONL, CSV, XML, RSS, SQLite, mbox, EML, Markdown, HTML, and Plain Text.
  • Group conversations: Organize replies into threads to follow discussions easily.
  • Clean content: Automatically remove signatures, old quotes, attachments, and color codes.
  • Protect privacy: Hide personal information or private messages.
  • Process many files: Convert several archives at once or merge them into one file.
  • Preview changes: Use "Dry Run" mode to see results before writing any files.
  • Read messages: Use the built-in graphical reader to browse archives without converting them.

Supported Formats

FormatImportExportNotes
QWK / REPClassic BBS packets (.qwk, .rep, .zip, .tar, .tar.gz, .tar.bz2, .tgz, messages.dat, reply.dat)
JSON / JSONLModern structured data (.json, .jsonl)
HTMLBrowsable files with conversation grouping and charts (.html, .htm)
MarkdownReadable text files (.md, .markdown)
CSVSpreadsheets and databases (.csv)
RSSFeed readers and syndication (.rss)
XMLGeneric structured data (.xml)
SQLiteRelational databases (.db, .sqlite)
mbox / EML / MaildirEmail applications (.mbox, .eml, .maildir)
Plain TextSimple readable text (.txt)

Prerequisites

Required

  • Python 3.10 or newer.

Optional

  • Tkinter: Needed for the graphical reader. Most Python installations already have it. Linux and macOS users may need to install it:
    • Ubuntu/Debian: sudo apt install python3-tk
    • Fedora: sudo dnf install python3-tkinter
    • Arch Linux: sudo pacman -S tk
    • macOS (Homebrew): brew install python-tk
  • tqdm: Adds a progress bar. Install with: python -m pip install tqdm
  • unzip: Helps open older ZIP archives. Install it if pyqwk cannot open your file:
    • Ubuntu/Debian: sudo apt install unzip
    • Fedora: sudo dnf install unzip
    • Arch Linux: sudo pacman -S unzip
    • macOS: brew install unzip
    • Windows: winget install GnuWin32.UnZip

Quick Start

Run the tool on any supported archive:

# Process a single archive
python qwk.py archive.qwk

# Process an entire folder
python qwk.py my_archives/

# Process a compressed TAR archive
python qwk.py messages.tar.gz

Installation

You can install pyqwk to use it from any folder.

Using a virtual environment keeps your Python packages organized. It prevents conflicts between different projects on your computer.

  1. Open your terminal in the pyqwk folder.
  2. Create a virtual environment:
    python -m venv venv
  3. Activate the virtual environment based on your operating system:
    • Windows (Command Prompt):
      venv\Scripts\activate.bat
    • Windows (PowerShell):
      venv\Scripts\Activate.ps1
    • macOS and Linux:
      source venv/bin/activate
  4. Install the package:
    python -m pip install .
  5. Run pyqwk using the command-line command:
    qwk archive.qwk
  6. Or start the graphical reader:
    qwk-gui

Option 2: Install System-Wide

If you prefer not to use a virtual environment, you can install the tool directly to your system's Python environment.

  1. Open your terminal in the pyqwk folder.
  2. Install the package:
    python -m pip install .
  3. Run the tool:
    qwk archive.qwk

Note: You can also run the reader without installing:

python -m pyqwk.gui

Graphical Reader

Use the built-in reader to browse conferences, search messages, and follow conversations.

To start the reader:

# Open the reader
qwk-gui

# Open a specific file
qwk-gui archive.qwk

# Open several archives at once
qwk-gui archive1.qwk archive2.qwk

# Open all archives in a folder
qwk-gui archives/

# Open a database
qwk-gui messages.db

Key Features:

  • Search: Find messages by keyword or use regular expressions. Cycle through matches with F3 or Shift + F3. The reader moves to the next or previous message when you reach the end of the current one.
  • Attachments: Click attachment links in the header to save files. Use File > Extract All Attachments... to save all files from your current view.
  • Filtering: Narrow your view by BBS, conference, author, recipient, word count, or messages sent "on this day." Use the Exclude field to hide messages matching specific keywords. You can also filter for private messages or messages with attachments. Quick toggles help you find messages with links, emails, phones, color codes, or specific word counts.
  • Context Menus: Right-click a message to copy its details, filter the view, or exclude specific authors and subjects.
  • Exporting: Save your current filtered view to any format (HTML, Markdown, JSON, etc.).
  • Viewing Options: Use "Conversations" to group replies into a threaded view. Use "Clean" view to hide signatures, quotes, attachments, and color codes. Use "Remove Colors" to strip only color codes. Use "Hide Personal Info" to redact emails and phone numbers. Use "Embed Attachments" to include images directly in messages.
  • Statistics: View detailed activity reports. You can also save these reports as HTML files with interactive charts.

Keyboard Shortcuts:

Archive & Stats

  • Ctrl + O: Open an archive.
  • Ctrl + S: Export the current filtered view.
  • Ctrl + I: View archive statistics and reports.
  • Ctrl + Q: Exit the application.

Search & Filters

  • Ctrl + F or /: Jump to the search bar.
  • Ctrl + E: Jump to the exclude bar.
  • F3: Find the next search match.
  • Shift + F3: Find the previous search match.
  • Enter: Find the next match when the search bar is focused.
  • Shift + Enter: Find the previous match when the search bar is focused.
  • Esc: Clear the search on the first press and all filters on the second press.
  • Ctrl + Shift + X: Clear all filters instantly.

Navigation

  • j or n: Move to the next message.
  • k or p: Move to the previous message.
  • Space or PgDn: Scroll down or move to the next message.
  • Shift + Space, BackSpace, or PgUp: Scroll up or move to the previous message.
  • Ctrl + G: Jump to a specific message number.
  • Ctrl + U: Go to the referenced parent message.
  • r: Select a random message.
  • [ or ]: Move to the previous or next conference.
  • { or }: Move to the previous or next BBS archive.

Workflow Presets

pyqwk includes presets for common tasks. Presets apply several settings at once to save you time.

Use the -P or --preset option followed by the preset name:

# Save messages as clean, threaded Markdown files (perfect for blogs)
qwk archive.qwk --preset blog -o my_blog/

# Save messages as individual EML files (for email clients)
qwk archive.qwk --preset email -o ./emails/

# Back up your archive into a single SQLite database with duplicates removed
qwk archive.qwk --preset backup -o backup.db

# Create a single threaded HTML digest with a table of contents
qwk archive.qwk --preset digest -o digest.html

# Save messages as clean, threaded individual HTML files with an index (static discussion board)
qwk archive.qwk --preset forum -o my_forum_dir/

# Save messages as a clean, chronological RSS feed sorted from newest to oldest
qwk archive.qwk --preset feed -o feed.xml

# Save a simple clean text archive with no headers
qwk archive.qwk --preset text-archive -o archive.txt

To list all available presets and their equivalent options directly in your terminal, use:

qwk --list-presets

Available Presets

PresetActionEquivalent Options
blogSaves clean, threaded Markdown files.--format markdown --clean --threaded --individual-files
emailSaves messages as individual EML files.--format eml --individual-files
backupCreates a SQLite backup with private and unique messages.--format sqlite --private --unique
digestSaves a single clean, threaded HTML file with a table of contents.--format html --threaded --clean --toc
forumSaves clean, threaded individual HTML files with an index.--format html --clean --threaded --individual-files --toc
feedSaves a clean chronological RSS feed.--format rss --clean --sort date --reverse
text-archiveSaves clean text without headers.--format text --clean --noheader

Note: You can override any preset setting by adding its command-line option.

Usage Examples

Read an archive:

qwk archive.qwk

Show a quick summary:

# Standard one-line summary
qwk archive.qwk --oneline

# Custom summary with specific information
qwk archive.qwk --oneline-pattern "[{confnum}] {author}: {subject}"

Save as a text file:

qwk archive.qwk -o messages.txt

Group messages into conversations:

qwk archive.qwk --threaded -o messages.txt

Create a browsable HTML file:

qwk archive.qwk --format html -o messages.html

Create a self-contained HTML file (includes images):

qwk archive.qwk --embed-attachments -o messages.html

Convert to an mbox file (for email apps):

qwk archive.qwk --format mbox -o messages.mbox

Save each message as a separate file:

qwk archive.qwk --individual-files -o output_folder/

Save separate files directly inside a compressed archive: You can save messages as individual files directly into a compressed archive like a ZIP or TAR file:

qwk archive.qwk --individual-files -o output.zip

Organize files by conference:

qwk archive.qwk --individual-files --organize -o output_folder/

Organize files by date:

qwk archive.qwk --individual-files --organize-by-date -o output_folder/

Organize files by author:

qwk archive.qwk --individual-files --organize-by-author -o output_folder/

Organize files by recipient:

qwk archive.qwk --individual-files --organize-by-to -o output_folder/

Organize files by subject:

qwk archive.qwk --individual-files --organize-by-subject -o output_folder/

Use a custom folder structure:

qwk archive.qwk --individual-files --organize-pattern "{year}/{month}/{author}" -o output_folder/

Use custom filenames:

qwk archive.qwk --individual-files --filename-pattern "{date}_{author}_{subject}" -o output_folder/

Merge archives and remove duplicates:

qwk archive1.qwk archive2.qwk --merge --unique -o combined.mbox

Clean up messages (removes signatures, quotes, attachments, and color codes):

qwk archive.qwk --clean -o clean.txt

Extract attachments to a folder:

qwk archive.qwk --extract-attachments -o output/

Hide personal information (emails and phones):

qwk archive.qwk --redact-pii -o safe.txt

Export to a database (SQLite):

qwk archive.qwk --format sqlite -o messages.db

Import from a spreadsheet (CSV):

qwk messages.csv -o updated.html

Export to JSONL (for big data and scripts):

qwk archive.qwk --format jsonl -o messages.jsonl

Create an RSS feed:

qwk archive.qwk --format rss -o feed.xml

Convert between modern formats (mbox to EML):

qwk messages.mbox --format eml -o ./emails/

Convert to Maildir:

qwk archive.qwk --format maildir -o ./my_maildir/

Check archive integrity (validate file):

qwk archive.qwk --validate

Export messages to a classic QWK packet:

qwk messages.mbox --format qwk -o output.qwk

Filtering & Searching

Filter by Conference:

qwk archive.qwk -C "General Chat"

Filter by BBS:

qwk my_archives/ --bbs "The Digital Horizon"

Keyword Search:

qwk archive.qwk --search "BBS"

Filter by Date:

# Between two specific dates
qwk archive.qwk --after 2023-01-01 --before 2023-12-31

# Messages from "this day" in any year
qwk archive.qwk --on-this-day

Limit per Conference:

# Show only the first 5 messages from each conference
qwk archive.qwk --limit-per-conf 5

Limit per Author:

# Show only the first 2 messages from each author
qwk archive.qwk --limit-per-author 2

Limit per Subject:

# Show only the first 3 messages from each subject
qwk archive.qwk --limit-per-subject 3

Limit per BBS:

# Show only the first 3 messages from each BBS
qwk my_archives/ --limit-per-bbs 3

Find Content:

# Show messages with links
qwk archive.qwk --has-links

# Show messages with phone numbers
qwk archive.qwk --has-phones

# Show messages with email addresses
qwk archive.qwk --has-emails

# Show messages with color codes
qwk archive.qwk --has-ansi

Filter by Person:

# Messages from or to specific names
qwk archive.qwk --from "Sysop" --to "Alice"

# Messages specifically for you (based on your user name)
qwk archive.qwk --mine

Filter by Length:

# At least 1000 characters
qwk archive.qwk --min-length 1000

# At least 2 attachments
qwk archive.qwk --min-attachments 2

Filter by Length (Words):

# At least 100 words
qwk archive.qwk --min-words 100

# Between 50 and 500 words
qwk archive.qwk --min-words 50 --max-words 500

Filter by Thread Depth:

# Show only original posts (depth 0)
qwk archive.qwk --threaded --max-depth 0

# Show messages at depth 2 or deeper
qwk archive.qwk --threaded --min-depth 2

Filter by Engagement:

# Show messages with at least 5 direct replies
qwk archive.qwk --min-replies 5

# Show messages from large conversations (at least 20 messages)
qwk archive.qwk --min-thread-size 20

Filter by Message Number:

qwk archive.qwk --msgnum 100-200

Filter by Referenced Message Number (Reply-To):

qwk archive.qwk --reply-to 100-200

Dry Run: Preview your changes without writing files:

qwk archives/ --search "BBS" --dry-run

Library Usage

You can use pyqwk as a library in your own Python projects:

import logging
from pyqwk.core import load_data, parse_messages, process_message

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("pyqwk")

# Load the archive
# file_data contains either original bytes (for QWK/REP) or a list of messages
file_data, board_dict = load_data("archive.qwk", logger)

# Parse the bytes if the archive is in an older format
if isinstance(file_data, list):
    messages = file_data
else:
    messages = parse_messages(file_data, None)

# Process messages
for msg in messages:
    # Remove signatures, quotes, and attachments
    clean_text = process_message(
        msg.text,
        truncate_signatures=True,
        cut_quoting=True,
        binaries_removal=True,
        redact_pii=False
    )

    conf_name = board_dict.get(msg.confnum, f"Conference {msg.confnum}")
    print(f"[{conf_name}] From: {msg.header.msgfrom}")
    print(clean_text)

Common Options

FlagDescription
-o, --outputSave results to a file or folder.
-v, --verboseShow more details like conference names and message numbers.
-i, --individual-filesSave each message as a separate file. This also creates a browsable index for HTML and Markdown.
-F, --formatSet the output format (html, json, markdown, etc.).
-P, --presetApply predefined parameter combinations for common workflows (blog, email, backup, digest, forum, feed, text-archive).
--list-presetsList all available presets, their descriptions, and equivalent command-line options, then exit.
--separatorSet how to separate messages in the output file (auto, none, dashes, blank).
-m, --mergeCombine multiple archives into one file.
-u, --uniqueRemove duplicate messages during a merge.
-T, --threadedGroup replies into threads.
--cleanRemove signatures, quotes, attachments, and color codes.
-x, --extract-attachmentsSave attachments to a folder.
--embed-attachmentsInclude image attachments directly in HTML files.
--organize-attachmentsOrganize extracted attachments into subfolders.
--organizeOrganize files into folders by conference.
--organize-by-dateOrganize files into folders by date (YYYY/MM).
--organize-by-bbsOrganize archives into folders named after the BBS. If used with -o, organizes the export folder instead.
--organize-by-authorOrganize files into folders by author name.
--organize-by-toOrganize files into folders by recipient name.
--organize-by-subjectOrganize files into folders by message subject.
--organize-patternSet a custom folder structure for individual files.
--filename-patternSet a custom filename pattern for individual files (e.g., {date}_{author}_{subject}).
-O, --sortSort results by field (date, author, to, subject, num, conference, bbs, length, size, random, words, or attachments).
-r, --redact-piiHide personal info like emails and phone numbers.
-p, --privateInclude private messages.
--mineShow messages sent to or from your user name.
--my-nameSet your name for the --mine filter and QWK exports.
--has-attachmentsShow messages that contain attachments.
--has-linksShow messages that contain links.
--has-emailsShow messages that contain email addresses.
--has-phonesShow messages that contain phone numbers.
--has-ansiShow messages that contain color codes.
--has-msg-linksShow messages that contain internal message links (e.g., 'msg #123').
-A, --strip-ansiRemove color codes and other formatting symbols.
-H, --headers-onlyShow the message details (metadata) without the body.
-E, --encodingSet the text encoding (default is cp437).
-S, --searchShow messages with specific keywords in any common field: Author, To, Subject, Body, Conference, BBS, BBS ID, Source File, and Attachments. Supports partial matches.
-C, --conferenceShow messages from a specific conference (name or number). Supports partial matches.
-B, --bbsShow messages from a specific BBS (name or ID). Supports partial matches.
-f, --fromShow messages from a specific author. Supports partial matches.
--toShow messages to a specific recipient. Supports partial matches.
-s, --subjectShow messages with specific keywords in the subject. Supports partial matches.
--bodyShow messages with specific keywords in the body. Supports partial matches.
-X, --excludeHide messages with specific keywords in any common field: Author, To, Subject, Body, Conference, BBS, BBS ID, Source File, and Attachments. Supports partial matches.
--exclude-fromHide messages from a specific author. Supports partial matches.
--exclude-toHide messages sent to a specific recipient. Supports partial matches.
--exclude-subjectHide messages with specific keywords in the subject. Supports partial matches.
--exclude-conferenceHide messages from a specific conference. Supports partial matches.
--exclude-bbsHide messages from a specific BBS. Supports partial matches.
--afterShow messages sent on or after a date (YYYY-MM-DD).
--beforeShow messages sent on or before a date (YYYY-MM-DD).
--limit-per-confLimit the number of matching messages per conference.
--limit-per-authorLimit the number of matching messages per author.
--limit-per-subjectLimit the number of matching messages per subject.
--limit-per-toLimit the number of matching messages per recipient.
--limit-per-bbsLimit the number of matching messages per BBS.
--tailShow the last NUM matching messages. Alias: --last.
--on-this-dayShow messages from the same month and day.
-N, --msgnumShow specific message numbers or ranges.
-R, --reply-to, --refnumShow messages that are a reply to specific reference/message numbers or ranges.
-L, --limitStop after NUM matching messages.
-K, --skipSkip the first NUM matching messages.
--regexUse regular expressions for search and filters.
--reverseReverse the sorting order.
--min-lengthShow messages with at least NUM characters.
--max-lengthShow messages with at most NUM characters.
--min-wordsShow messages with at least NUM words.
--min-attachmentsShow messages with at least NUM attachments.
--max-wordsShow messages with at most NUM words.
--max-attachmentsShow messages with at most NUM attachments.
--min-depthShow messages with a thread depth of at least NUM.
--max-depthShow messages with a thread depth of at most NUM.
--min-repliesShow messages with at least NUM direct replies.
--max-repliesShow messages with at most NUM direct replies.
--min-thread-sizeShow messages belonging to a conversation with at least NUM messages.
--max-thread-sizeShow messages belonging to a conversation with at most NUM messages.
--tocAdd a table of contents to the output.
-1, --onelineShow a one-line summary (Conf, Date, From, To, Flags, Subject). Use with --verbose to include the message number.
--oneline-patternSet a custom pattern for one-line summaries.
-I, --infoShow a summary of the archive and exit.
--statsShow message statistics and exit.
--merge-statsShow a single merged report for multiple archives.
--dry-runPreview actions without writing files.

Run qwk --help for all options.

Custom Pattern Variables

You can use custom patterns with --oneline-pattern (for summaries on the screen) and --filename-pattern (for naming individual files).

Basic Information

VariableDescription
{author}The name of the person who sent the message.
{to}The name of the recipient.
{subject}The original subject line.
{subject_clean}The subject line without "Re:" or "Fwd:" prefixes.
{body}The full text of the message body.
{body_clean}The message body with all whitespace collapsed into single spaces.
{confname}The name of the conference (if known).
{confnum}The number of the conference.
{confname_or_num}The conference name, or its number if the name is missing.
{msgnum}The unique message number.
{snippet}The first line of the message body.
{url_count}The number of web links found in the message.
{email_count}The number of email addresses found in the message.
{phone_count}The number of phone numbers found in the message.
{msg_link_count}The number of message references found in the text.
{my_name}The user name (either from archive information or your override).
{urls}A comma-separated list of web links found in the message.
{emails}A comma-separated list of email addresses found in the message.
{phones}A comma-separated list of phone numbers found in the message.
{msg_links}A list of message numbers referenced in the text.

Dates & Times

VariableDescription
{date}The date in MM-DD-YY format.
{time}The time in HH:MM format.
{year}, {month}, {day}Individual date parts (e.g., 2023, 10, 12).
{hour}, {minute}, {second}Individual time parts.
{iso_date}, {iso_time}Date and time in standard ISO format.

BBS & Source

VariableDescription
{bbs_name}The name of the BBS where the message originated.
{bbs_id}The short ID of the BBS.
{source_file}The name of the archive file that contained the message.

Technical Details

VariableDescription
{msgid}A unique identifier for the message (conf.msg@bbs).
{refnum}The message number being replied to.
{status}The status code (e.g., * for private).
{msgflag}Technical flags from the message header.
{is_private}Returns true or false.
{is_reply}Returns true if the message is a reply.
{thread_id}A unique ID for the conversation thread.
{parent_msgnum}The message number of the post being replied to.
{depth}The depth of the message in the conversation (0 for original posts).
{length}The number of characters in the message.
{word_count}The number of words in the message text.
{size}The readable size of the message (e.g., 1.2 KB).
{flags}Short indicators (e.g., * for private, @ for attachments).
{indent}Spaces and symbols used for organizing conversations on the screen.
{reply_count}The number of direct replies to this message.
{thread_size}The total number of messages in this conversation.

Attachments

VariableDescription
{attachments}A list of all attachment filenames.
{attachment_count}The number of files attached to the message.

Troubleshooting

  • If a file will not open: If pyqwk cannot open an archive, install unzip. If it still fails, unzip the file manually and run qwk on the messages.dat file inside.
  • If characters look wrong: If you see incorrect characters, use the --encoding flag (for example, --encoding cp850).
  • If options do not work together: Some options cannot be used together:
    • You cannot use --oneline and --individual-files at the same time.
    • You cannot use --threaded with eml or maildir formats.
  • If you get folder errors: When you save messages as individual files, or when you use the eml or maildir formats, the output path (-o or --output) must be a folder or a compressed archive (such as .zip, .tar.gz, .tar.bz2, or .tgz). If you provide a regular file path (like .txt or .html) for these multi-file formats, the tool will show an error.

Contributing

We welcome your contributions!

  1. Install development tools:
    python -m pip install -e . pytest pytest-mock pytest-cov
  2. Run tests:
    python -m pytest

Headless Testing

If you are running the test suite on a headless Linux system (such as in a CI/CD environment or a remote server without an active display), you must run tests using xvfb-run.

  1. Install the headless dependencies:
    python -m pip install mss Pillow
  2. Run the tests:
    xvfb-run -a python3 -m pytest