144 lines
4 KiB
Go
144 lines
4 KiB
Go
// bulletins.go implements the bulletin subsystem.
|
|
//
|
|
// This replaces BCOM.C from the original TAG-BBS. Bulletins are
|
|
// read-only text (or ANSI art) files displayed to users. The sysop
|
|
// creates them outside the BBS and registers them in the database.
|
|
//
|
|
// The original stored bulletins as a linked list of Bulletin_Header
|
|
// structs loaded from System.Data at startup. Each had a Filename
|
|
// and Location (directory path) plus ReadLow/ReadHigh access levels.
|
|
// Reading a bulletin called MenuSend() which opened the file and
|
|
// sent it line-by-line with flow control.
|
|
//
|
|
// Our version stores bulletin metadata in SQLite and reads the actual
|
|
// files from the FilePath column. The session's SendFile method
|
|
// handles ANSI display and flow control.
|
|
package menu
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/urit/urit/internal/models"
|
|
"github.com/urit/urit/internal/session"
|
|
)
|
|
|
|
// cmdBulletins is the entry point for the bulletin subsystem.
|
|
// Replaces BCom() from BCOM.C.
|
|
func cmdBulletins(ctx *Context) error {
|
|
bulletins, err := ctx.Store.ListBulletins()
|
|
if err != nil {
|
|
ctx.Sess.WriteString(" Error loading bulletins.\r\n")
|
|
return nil
|
|
}
|
|
|
|
// Filter to those the user can read
|
|
var visible []*models.Bulletin
|
|
for _, b := range bulletins {
|
|
if ctx.User.SecBulletin >= b.ReadLow && ctx.User.SecBulletin <= b.ReadHigh {
|
|
visible = append(visible, b)
|
|
}
|
|
}
|
|
|
|
if len(visible) == 0 {
|
|
ctx.Sess.WriteString(" No bulletins available.\r\n\r\n")
|
|
return nil
|
|
}
|
|
|
|
ctx.Sess.NewLine()
|
|
|
|
for {
|
|
ctx.Sess.CheckTime()
|
|
ctx.Sess.Color(session.AnsiFgCyan)
|
|
ctx.Sess.WriteString("L>ist Q>uit\r\n")
|
|
ctx.Sess.Printf("Bulletin to read [1-%d]: ", len(visible))
|
|
ctx.Sess.Color(session.AnsiReset)
|
|
|
|
// Read a line (not a single key) so users can type a number
|
|
// — matches original: LineInput("",string,5,120L)
|
|
input, err := ctx.Sess.ReadLine("", 5, inputTimeout)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
input = strings.TrimSpace(input)
|
|
|
|
if input == "" {
|
|
continue
|
|
}
|
|
|
|
switch {
|
|
case strings.EqualFold(input, "L"):
|
|
bulletinList(ctx, visible)
|
|
|
|
case strings.EqualFold(input, "Q") || input == "@":
|
|
return nil
|
|
|
|
case input == "?" || input == "/":
|
|
bulletinList(ctx, visible)
|
|
|
|
default:
|
|
num := parseIntDefault(input, 0)
|
|
if num < 1 || num > len(visible) {
|
|
ctx.Sess.Printf(" Enter 1-%d, L to list, Q to quit.\r\n", len(visible))
|
|
continue
|
|
}
|
|
bulletinRead(ctx, visible[num-1], num)
|
|
}
|
|
}
|
|
}
|
|
|
|
// bulletinList shows all accessible bulletins.
|
|
// Replaces Bulletin_Listings() from BCOM.C.
|
|
func bulletinList(ctx *Context, bulletins []*models.Bulletin) {
|
|
ctx.Sess.NewLine()
|
|
ctx.Sess.Color(session.AnsiFgGreen, session.AnsiBold)
|
|
ctx.Sess.WriteString("Bulletin Listings\r\n")
|
|
ctx.Sess.Color(session.AnsiReset)
|
|
ctx.Sess.WriteString(strings.Repeat("─", 40) + "\r\n")
|
|
|
|
for i, b := range bulletins {
|
|
ctx.Sess.Printf(" [%d] %s\r\n", i+1, b.Name)
|
|
}
|
|
ctx.Sess.NewLine()
|
|
}
|
|
|
|
// bulletinRead displays a single bulletin file.
|
|
// Replaces Read_Bulletin() from BCOM.C — the original just called
|
|
// MenuSend(location + filename) which sent the file with flow control.
|
|
func bulletinRead(ctx *Context, b *models.Bulletin, num int) {
|
|
ctx.Sess.NewLine()
|
|
ctx.Sess.Color(session.AnsiFgBrightWhite)
|
|
ctx.Sess.Printf("Bulletin #%d: %s\r\n", num, b.Name)
|
|
ctx.Sess.Color(session.AnsiReset)
|
|
ctx.Sess.WriteString(strings.Repeat("─", 40) + "\r\n")
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(b.FilePath); err != nil {
|
|
ctx.Sess.Color(session.AnsiFgRed)
|
|
ctx.Sess.Printf(" File not found: %s\r\n", b.FilePath)
|
|
ctx.Sess.Color(session.AnsiReset)
|
|
return
|
|
}
|
|
|
|
// Read and display the file
|
|
data, err := os.ReadFile(b.FilePath)
|
|
if err != nil {
|
|
ctx.Sess.WriteString(" Error reading bulletin file.\r\n")
|
|
return
|
|
}
|
|
|
|
// Display with pagination
|
|
content := string(data)
|
|
ctx.Sess.Paginate(content, idleTimeout)
|
|
ctx.Sess.NewLine()
|
|
|
|
// Pause after display — classic BBS "press any key"
|
|
ctx.Sess.WaitForKey(
|
|
fmt.Sprintf("%s--- End of Bulletin #%d ---%s Press any key... ",
|
|
session.AnsiFgBrightBlack, num, session.AnsiReset),
|
|
idleTimeout,
|
|
)
|
|
ctx.Sess.NewLine()
|
|
}
|