Documentation
¶
Index ¶
- Constants
- Variables
- func AddCustomTearline(text, tearline string) string
- func AddOriginLine(text, systemName, address string) string
- func AddTearline(text string) string
- func CP437ToUnicode(input []byte) string
- func CRC32String(s string) uint32
- func FormatPID() string
- func FormatTID() string
- type Base
- func (b *Base) Close() error
- func (b *Base) DeleteMessage(msgNum int) error
- func (b *Base) GenerateMSGID(origAddr string) (string, error)
- func (b *Base) GetActiveMessageCount() int
- func (b *Base) GetAllLastReadRecords() ([]LastReadRecord, error)
- func (b *Base) GetFixedHeader() *FixedHeaderInfo
- func (b *Base) GetLastRead(username string) (*LastReadRecord, error)
- func (b *Base) GetMessageCount() (int, error)
- func (b *Base) GetModCounter() (uint32, error)
- func (b *Base) GetNextMsgSerial() (uint32, error)
- func (b *Base) GetNextUnreadMessage(username string) (int, error)
- func (b *Base) GetUnreadCount(username string) (int, error)
- func (b *Base) IsOpen() bool
- func (b *Base) Link() (LinkResult, error)
- func (b *Base) MarkMessageRead(username string, msgNum int) error
- func (b *Base) Pack() (PackResult, error)
- func (b *Base) PackWithReplyIDCleanup() (PackResult, error)
- func (b *Base) ReadIndexRecord(msgNum int) (*IndexRecord, error)
- func (b *Base) ReadMessage(msgNum int) (*Message, error)
- func (b *Base) ReadMessageHeader(msgNum int) (*MessageHeader, error)
- func (b *Base) ReadMessageText(hdr *MessageHeader) (string, error)
- func (b *Base) RefreshFixedHeader() (*FixedHeaderInfo, error)
- func (b *Base) ResetLastRead(username string) error
- func (b *Base) ScanMessages(startMsg, maxMessages int) ([]*Message, error)
- func (b *Base) SetLastRead(username string, lastRead, highRead uint32) error
- func (b *Base) UpdateMessageHeader(msgNum int, hdr *MessageHeader) error
- func (b *Base) WriteMessage(msg *Message) (int, error)
- func (b *Base) WriteMessageExt(msg *Message, msgType MessageType, echoTag, bbsName, tearline string) (int, error)
- type FidoAddress
- type FixedHeaderInfo
- type IndexRecord
- type LastReadRecord
- type LinkResult
- type Message
- type MessageHeader
- type MessageType
- type PackResult
- type Subfield
Constants ¶
const ( Signature = "JAM\x00" HeaderSize = 1024 // Fixed header occupies first 1024 bytes of .jhr FixedHeaderSize = 76 // Actual data size within the fixed header SubfieldHdrSize = 8 // LoID(2) + HiID(2) + DatLen(4) IndexRecordSize = 8 // ToCRC(4) + HdrOffset(4) LastReadSize = 16 // UserCRC(4) + UserID(4) + LastReadMsg(4) + HighReadMsg(4) )
JAM file format constants
const ( MsgLocal = 0x00000001 // Created locally MsgInTransit = 0x00000002 // In-transit MsgPrivate = 0x00000004 // Private message MsgRead = 0x00000008 // Read by addressee MsgSent = 0x00000010 // Sent to remote MsgKillSent = 0x00000020 // Kill when sent MsgArchiveSent = 0x00000040 // Archive when sent MsgHold = 0x00000080 // Hold for pick-up MsgCrash = 0x00000100 // Crash MsgImmediate = 0x00000200 // Send immediately MsgDirect = 0x00000400 // Send directly MsgGate = 0x00000800 // Send via gateway MsgFileRequest = 0x00001000 // File request MsgFileAttach = 0x00002000 // File(s) attached MsgTruncFile = 0x00004000 // Truncate file(s) MsgKillFile = 0x00008000 // Delete file(s) MsgReceiptReq = 0x00010000 // Return receipt requested MsgConfirmReq = 0x00020000 // Confirmation receipt requested MsgOrphan = 0x00040000 // Unknown destination MsgEncrypt = 0x00080000 // Encrypted MsgCompress = 0x00100000 // Compressed MsgEscaped = 0x00200000 // Seven bit ASCII MsgFPU = 0x00400000 // Force pickup MsgTypeLocal = 0x00800000 // Local use only MsgTypeEcho = 0x01000000 // Conference/echo mail MsgTypeNet = 0x02000000 // Direct network mail MsgNoDisp = 0x20000000 // May not be displayed MsgLocked = 0x40000000 // Locked MsgDeleted = 0x80000000 // Deleted )
Message attribute flags from JAM specification
const ( SfldOAddress = 0 // Origin network address SfldDAddress = 1 // Destination network address SfldSenderName = 2 // Sender name SfldReceiverName = 3 // Receiver name SfldMsgID = 4 // Message ID (FTN MSGID) SfldReplyID = 5 // Reply ID (FTN REPLY) SfldSubject = 6 // Subject SfldPID = 7 // Program ID SfldTrace = 8 // Trace info SfldFTSKludge = 2000 // FTN kludge line SfldSeenBy2D = 2001 // SEEN-BY in 2D format SfldPath2D = 2002 // PATH in 2D format SfldFlags = 2003 // Message flags SfldTZUTCInfo = 2004 // Timezone/UTC info )
Subfield type identifiers from JAM specification
Variables ¶
var ( ErrInvalidSignature = errors.New("jam: invalid JAM signature") ErrInvalidMessage = errors.New("jam: invalid message number") ErrBaseNotOpen = errors.New("jam: message base not open") ErrNotFound = errors.New("jam: not found") )
Sentinel errors
var Version = version.Number
Version is kept for compatibility; use internal/version.Number for new code.
Functions ¶
func AddCustomTearline ¶
AddCustomTearline appends a tearline to the message text. If tearline is empty, it uses the default ViSiON/3 tearline. If tearline already starts with "---", it is used as-is.
func AddOriginLine ¶
AddOriginLine appends an origin line to the message text. Format: " * Origin: BBS Name (1:103/705)"
func AddTearline ¶
AddTearline appends a tearline to the message text. Format: "--- ViSiON/3 0.1.0/darwin"
func CP437ToUnicode ¶
CP437ToUnicode converts CP437-encoded bytes to a Unicode string.
func CRC32String ¶
CRC32String calculates a JAM-specification CRC32 of a string. Per the JAM spec: lowercase only A-Z (not locale-aware), use IEEE polynomial, and invert the result.
Types ¶
type Base ¶
type Base struct {
BasePath string
// contains filtered or unexported fields
}
Base represents an open JAM message base backed by four files: .jhr (headers), .jdt (text), .jdx (index), .jlr (lastread).
func Open ¶
Open opens an existing JAM message base or creates a new one if it does not exist. basePath is the path without file extension (e.g., "data/msgbases/general").
func (*Base) DeleteMessage ¶
DeleteMessage marks a message as deleted and zeroes its text length.
func (*Base) GenerateMSGID ¶
GenerateMSGID creates a unique FTN-compatible MSGID using the base's serial counter. Format: "address hexserial" (e.g., "1:103/705 0012ab34"). Acquires b.mu internally; do not call while holding b.mu.
func (*Base) GetActiveMessageCount ¶
GetActiveMessageCount returns the number of non-deleted messages.
func (*Base) GetAllLastReadRecords ¶
func (b *Base) GetAllLastReadRecords() ([]LastReadRecord, error)
GetAllLastReadRecords reads all lastread records from the .jlr file.
func (*Base) GetFixedHeader ¶
func (b *Base) GetFixedHeader() *FixedHeaderInfo
GetFixedHeader returns the fixed header info for the base.
func (*Base) GetLastRead ¶
func (b *Base) GetLastRead(username string) (*LastReadRecord, error)
GetLastRead returns the lastread record for the given username. Returns ErrNotFound if the user has no record in this base.
func (*Base) GetMessageCount ¶
GetMessageCount returns the total number of messages (including deleted) by computing the number of index records in the .jdx file.
func (*Base) GetModCounter ¶
GetModCounter returns the current ModCounter value from disk.
func (*Base) GetNextMsgSerial ¶
GetNextMsgSerial atomically increments and returns the next MSGID serial. The serial counter is stored in Reserved[0:4] of the fixed header.
func (*Base) GetNextUnreadMessage ¶
GetNextUnreadMessage returns the next unread message number for the user. Returns ErrNotFound if there are no unread messages.
func (*Base) GetUnreadCount ¶
GetUnreadCount returns the number of unread messages for the user.
func (*Base) Link ¶
func (b *Base) Link() (LinkResult, error)
Link rebuilds reply threading chains (ReplyTo/Reply1st/ReplyNext) by matching MSGID and ReplyID subfields across all active messages. This should be called after Pack or after deleting messages to keep threading consistent.
func (*Base) MarkMessageRead ¶
MarkMessageRead updates the lastread pointer after reading a message.
func (*Base) Pack ¶
func (b *Base) Pack() (PackResult, error)
Pack defragments the message base by rewriting all non-deleted messages to new files, then atomically replacing the originals. The .jlr file is preserved as-is.
func (*Base) PackWithReplyIDCleanup ¶
func (b *Base) PackWithReplyIDCleanup() (PackResult, error)
PackWithReplyIDCleanup performs a pack operation while cleaning malformed ReplyIDs.
func (*Base) ReadIndexRecord ¶
func (b *Base) ReadIndexRecord(msgNum int) (*IndexRecord, error)
ReadIndexRecord reads the index record for a 1-based message number.
func (*Base) ReadMessage ¶
ReadMessage reads a complete message (header + subfields + text) for the given 1-based message number.
func (*Base) ReadMessageHeader ¶
func (b *Base) ReadMessageHeader(msgNum int) (*MessageHeader, error)
ReadMessageHeader reads a message header for the given 1-based message number.
func (*Base) ReadMessageText ¶
func (b *Base) ReadMessageText(hdr *MessageHeader) (string, error)
ReadMessageText reads the raw message text (CP437) for the given header.
func (*Base) RefreshFixedHeader ¶
func (b *Base) RefreshFixedHeader() (*FixedHeaderInfo, error)
RefreshFixedHeader reloads the fixed header from disk. Useful when external tools modify the base.
func (*Base) ResetLastRead ¶
ResetLastRead zeroes a user's lastread and highread pointers.
func (*Base) ScanMessages ¶
ScanMessages reads up to maxMessages starting from startMsg (1-based), skipping deleted messages. If maxMessages is 0, reads all.
func (*Base) SetLastRead ¶
SetLastRead updates or creates a lastread record for the given username.
func (*Base) UpdateMessageHeader ¶
func (b *Base) UpdateMessageHeader(msgNum int, hdr *MessageHeader) error
UpdateMessageHeader rewrites an existing message header in place. This is used by the tosser to update DateProcessed after export.
func (*Base) WriteMessage ¶
WriteMessage writes a complete local message to the base. Returns the 1-based message number assigned to the new message.
func (*Base) WriteMessageExt ¶
func (b *Base) WriteMessageExt(msg *Message, msgType MessageType, echoTag, bbsName, tearline string) (int, error)
WriteMessageExt writes a message with full echomail support. For echomail messages it generates a MSGID, adds AREA/PID/TID kludges, tearline, and origin line. DateProcessed is set to 0 so the tosser knows to export it.
For local messages this behaves identically to WriteMessage.
type FidoAddress ¶
FidoAddress represents a parsed FidoNet 4D address (Zone:Net/Node.Point).
func ParseAddress ¶
func ParseAddress(addr string) (*FidoAddress, error)
ParseAddress parses a FidoNet address string in the format "Z:N/N" or "Z:N/N.P".
func (*FidoAddress) String ¶
func (a *FidoAddress) String() string
String returns the full 4D address. Point is omitted if zero.
func (*FidoAddress) String2D ¶
func (a *FidoAddress) String2D() string
String2D returns the 2D address format (net/node) used for SEEN-BY and PATH.
type FixedHeaderInfo ¶
type FixedHeaderInfo struct {
Signature [4]byte
DateCreated uint32
ModCounter uint32
ActiveMsgs uint32
PasswordCRC uint32
BaseMsgNum uint32
Reserved [1000]byte
}
FixedHeaderInfo represents the JAM base header (1024 bytes on disk). The first 76 bytes contain structured data; bytes 76-1023 are reserved. Reserved[0:4] stores the MSGID serial counter.
type IndexRecord ¶
type IndexRecord struct {
ToCRC uint32 // CRC32 of lowercase recipient name
HdrOffset uint32 // Byte offset of header in .jhr
}
IndexRecord represents an entry in the .jdx index file (8 bytes).
type LastReadRecord ¶
type LastReadRecord struct {
UserCRC uint32 // CRC32 of lowercase username
UserID uint32
LastReadMsg uint32
HighReadMsg uint32
}
LastReadRecord represents a per-user lastread entry in the .jlr file (16 bytes).
type LinkResult ¶
LinkResult contains statistics from a Link operation.
type Message ¶
type Message struct {
Header *MessageHeader
From string
To string
Subject string
DateTime time.Time
Text string // CP437-encoded message body
OrigAddr string // FidoNet origin address
DestAddr string // FidoNet destination address
MsgID string
ReplyID string
ReplyTo uint32 // JAM parent message number (0 = none); written to the header
PID string
Flags string
SeenBy string
Path string
Kludges []string
}
Message is a high-level message structure combining header data, subfield-parsed fields, and message text.
func (*Message) GetAttribute ¶
GetAttribute returns the JAM attribute flags for this message.
type MessageHeader ¶
type MessageHeader struct {
Signature [4]byte
Revision uint16
ReservedWord uint16
SubfieldLen uint32
TimesRead uint32
MSGIDcrc uint32
REPLYcrc uint32
ReplyTo uint32
Reply1st uint32
ReplyNext uint32
DateWritten uint32
DateReceived uint32
DateProcessed uint32
MessageNumber uint32
Attribute uint32
Attribute2 uint32
Offset uint32 // Offset into .jdt file
TxtLen uint32 // Length of text in .jdt
PasswordCRC uint32
Cost uint32
Subfields []Subfield
}
MessageHeader represents a JAM message header stored in the .jhr file.
func (*MessageHeader) GetAllSubfieldsByType ¶
func (h *MessageHeader) GetAllSubfieldsByType(fieldType uint16) []Subfield
GetAllSubfieldsByType returns all subfields matching the given type.
func (*MessageHeader) GetSubfieldByType ¶
func (h *MessageHeader) GetSubfieldByType(fieldType uint16) *Subfield
GetSubfieldByType returns the first subfield matching the given type, or nil if none is found.
type MessageType ¶
type MessageType int
MessageType represents the type of message being created.
const ( MsgTypeLocalMsg MessageType = iota // Local BBS-only message MsgTypeEchomailMsg // FTN conference/echo message MsgTypeNetmailMsg // FTN direct network mail )
func DetermineMessageType ¶
func DetermineMessageType(areaType, echoTag string) MessageType
DetermineMessageType returns the MessageType based on area configuration.
func (MessageType) GetJAMAttribute ¶
func (mt MessageType) GetJAMAttribute() uint32
GetJAMAttribute returns the JAM attribute flags for this message type.
func (MessageType) IsEchomail ¶
func (mt MessageType) IsEchomail() bool
IsEchomail reports whether this is an echomail message.
func (MessageType) IsLocal ¶
func (mt MessageType) IsLocal() bool
IsLocal reports whether this is a local message.
func (MessageType) IsNetmail ¶
func (mt MessageType) IsNetmail() bool
IsNetmail reports whether this is a netmail message.
type PackResult ¶
type PackResult struct {
MessagesBefore int
MessagesAfter int
DeletedRemoved int
BytesBefore int64
BytesAfter int64
}
PackResult contains statistics from a Pack operation.
type Subfield ¶
Subfield represents a variable-length field attached to a message header.
func CreateSubfield ¶
CreateSubfield creates a Subfield from a type identifier and string data.