refact round 3

This commit is contained in:
2026-03-11 20:42:07 +01:00
parent d843df816a
commit 2157425b24
15 changed files with 477 additions and 413 deletions

45
content/data.catalog.go Normal file
View File

@@ -0,0 +1,45 @@
package content
import (
"encoding/json"
"net/http"
"time"
)
const GamesAPIURL = "https://games.teletype.hu/api/software"
type softwareEntry struct {
Software struct {
Title string `json:"title"`
Platform string `json:"platform"`
Author string `json:"author"`
Desc string `json:"desc"`
} `json:"software"`
Releases []interface{} `json:"releases"`
LatestRelease *struct {
Version string `json:"version"`
HTMLFolderPath string `json:"htmlFolderPath"`
CartridgePath string `json:"cartridgePath"`
SourcePath string `json:"sourcePath"`
DocsFolderPath string `json:"docsFolderPath"`
} `json:"latestRelease"`
}
type catalogRepository struct{}
func (r *catalogRepository) fetchGames() ([]softwareEntry, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(GamesAPIURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Softwares []softwareEntry `json:"softwares"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Softwares, nil
}

66
content/data.message.go Normal file
View File

@@ -0,0 +1,66 @@
package content
import (
"encoding/csv"
"os"
"sync"
)
type message struct {
Timestamp string
User string
Text string
}
// MessageBoard holds message board data
type MessageBoard struct {
messages []message
mu sync.Mutex
path string
}
// NewMessageBoard loads messages from the given .dat file
func NewMessageBoard(path string) *MessageBoard {
b := &MessageBoard{path: path}
b.load()
return b
}
func (b *MessageBoard) load() {
f, err := os.Open(b.path)
if err != nil {
return
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return
}
for _, row := range records {
if len(row) != 3 {
continue
}
b.messages = append(b.messages, message{Timestamp: row[0], User: row[1], Text: row[2]})
}
}
func (b *MessageBoard) append(msg message) error {
f, err := os.OpenFile(b.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
w := csv.NewWriter(f)
err = w.Write([]string{msg.Timestamp, msg.User, msg.Text})
w.Flush()
return err
}
func (b *MessageBoard) Count() int {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.messages)
}

102
content/data.wiki.go Normal file
View File

@@ -0,0 +1,102 @@
package content
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const WikiJSBaseURL = "https://wiki.teletype.hu"
type wikiPage struct {
ID interface{} `json:"id"`
Path string `json:"path"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Locale string `json:"locale"`
}
type wikiRepository struct {
token string
}
func (r *wikiRepository) graphql(query string) (map[string]interface{}, error) {
payload := map[string]string{"query": query}
data, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", WikiJSBaseURL+"/graphql", bytes.NewBuffer(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if r.token != "" {
req.Header.Set("Authorization", "Bearer "+r.token)
}
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func (r *wikiRepository) fetchList(tag string) ([]wikiPage, error) {
q := fmt.Sprintf(`{ pages { list(orderBy: CREATED, orderByDirection: DESC, tags: ["%s"]) { id path title description createdAt updatedAt locale } } }`, tag)
res, err := r.graphql(q)
if err != nil {
return nil, err
}
data, ok := res["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
pages, ok := data["pages"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
listRaw, ok := pages["list"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
var list []wikiPage
jsonBytes, _ := json.Marshal(listRaw)
json.Unmarshal(jsonBytes, &list)
return list, nil
}
func (r *wikiRepository) fetchContent(pageID interface{}) (string, error) {
var idStr string
switch v := pageID.(type) {
case string:
idStr = v
case float64:
idStr = fmt.Sprintf("%.0f", v)
default:
idStr = fmt.Sprintf("%v", v)
}
q := fmt.Sprintf(`{ pages { single(id: %s) { content } } }`, idStr)
res, err := r.graphql(q)
if err != nil {
return "", err
}
data := res["data"].(map[string]interface{})
pages := data["pages"].(map[string]interface{})
single := pages["single"].(map[string]interface{})
return single["content"].(string), nil
}

108
content/handler.blog.go Normal file
View File

@@ -0,0 +1,108 @@
package content
import (
"bbs-server/engine"
"fmt"
"strconv"
"strings"
)
// BlogHandler handles the Blog Posts menu item
type BlogHandler struct {
repo *wikiRepository
}
func NewBlogHandler(token string) *BlogHandler {
return &BlogHandler{repo: &wikiRepository{token: token}}
}
func (h *BlogHandler) Handle(s *engine.Session) {
renderWikiList(s, h.repo, "blog", "Blog Posts", engine.BL)
}
// renderWikiList is a helper used by wiki-based handlers
func renderWikiList(s *engine.Session, repo *wikiRepository, tag, title, color string) {
s.Printer.BoxHeader(title, color)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))
pages, err := repo.fetchList(tag)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%s%s: %v%s\r\n", engine.RD, s.Lang["WikiConnError"], err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
if len(pages) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, fmt.Sprintf(s.Lang["WikiNoResults"], tag), engine.R))
s.Printer.Pause(s.Lang)
return
}
maxIdx := 25
if len(pages) < maxIdx {
maxIdx = len(pages)
}
for i, p := range pages[:maxIdx] {
date := s.Printer.FmtDate(p.CreatedAt)
if date == "" {
date = s.Printer.FmtDate(p.UpdatedAt)
}
titleP := p.Title
if titleP == "" {
titleP = p.Path
}
if len(titleP) > 55 {
titleP = titleP[:55]
}
desc := p.Description
if len(desc) > 60 {
desc = desc[:60]
}
s.Printer.Send(fmt.Sprintf(" %s%2d%s %s%s%s\r\n", color, i+1, engine.R, engine.WH, titleP, engine.R))
if desc != "" {
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, date, engine.DIM, desc, engine.R))
} else {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, date, engine.R))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.GY, s.Lang["WikiEnterNum"], engine.R))
choice, _ := s.Printer.ReadLine()
choice = strings.TrimSpace(choice)
idx, err := strconv.Atoi(choice)
if err != nil || idx < 1 || idx > len(pages) {
return
}
page := pages[idx-1]
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s", engine.GY, s.Lang["WikiFetchContent"], engine.R))
pageContent, err := repo.fetchContent(page.ID)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%sHiba: %v%s\r\n", engine.RD, err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
s.Printer.Send("\r\n")
s.Printer.HR("═", color)
s.Printer.Send("\r\n")
s.Printer.Send(fmt.Sprintf(" %s%s%s%s\r\n", engine.WH, engine.B, page.Title, engine.R))
url := fmt.Sprintf("%s/%s/%s", WikiJSBaseURL, page.Locale, page.Path)
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, s.Printer.FmtDate(page.CreatedAt), engine.DIM, url, engine.R))
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n\r\n")
body := s.Printer.Wrapped(pageContent, 2, 5000)
for _, line := range strings.Split(body, "\r\n") {
s.Printer.Send(line + "\r\n")
}
s.Printer.Send("\r\n")
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n")
s.Printer.Pause(s.Lang)
}

View File

@@ -2,52 +2,11 @@ package content
import (
"bbs-server/engine"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
const GamesAPIURL = "https://games.teletype.hu/api/software"
type softwareEntry struct {
Software struct {
Title string `json:"title"`
Platform string `json:"platform"`
Author string `json:"author"`
Desc string `json:"desc"`
} `json:"software"`
Releases []interface{} `json:"releases"`
LatestRelease *struct {
Version string `json:"version"`
HTMLFolderPath string `json:"htmlFolderPath"`
CartridgePath string `json:"cartridgePath"`
SourcePath string `json:"sourcePath"`
DocsFolderPath string `json:"docsFolderPath"`
} `json:"latestRelease"`
}
type catalogRepository struct{}
func (r *catalogRepository) fetchGames() ([]softwareEntry, error) {
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Get(GamesAPIURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Softwares []softwareEntry `json:"softwares"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Softwares, nil
}
// CatalogHandler renders the game catalog
// CatalogHandler handles the Game Catalog menu item
type CatalogHandler struct {
repo *catalogRepository
}
@@ -56,8 +15,7 @@ func NewCatalogHandler() *CatalogHandler {
return &CatalogHandler{repo: &catalogRepository{}}
}
// Show displays the game catalog
func (h *CatalogHandler) Show(s *engine.Session) {
func (h *CatalogHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["CatTitle"], engine.YL)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))

8
content/handler.go Normal file
View File

@@ -0,0 +1,8 @@
package content
import "bbs-server/engine"
// Handler is the common interface for all BBS menu handlers
type Handler interface {
Handle(s *engine.Session)
}

16
content/handler.howtos.go Normal file
View File

@@ -0,0 +1,16 @@
package content
import "bbs-server/engine"
// HowToHandler handles the HowTo Guides menu item
type HowToHandler struct {
repo *wikiRepository
}
func NewHowToHandler(token string) *HowToHandler {
return &HowToHandler{repo: &wikiRepository{token: token}}
}
func (h *HowToHandler) Handle(s *engine.Session) {
renderWikiList(s, h.repo, "howto", "HowTo Guides", engine.MG)
}

View File

@@ -0,0 +1,40 @@
package content
import (
"bbs-server/engine"
"fmt"
)
// MessageBoardIndexHandler displays the message board posts
type MessageBoardIndexHandler struct {
board *MessageBoard
}
func NewMessageBoardIndexHandler(board *MessageBoard) *MessageBoardIndexHandler {
return &MessageBoardIndexHandler{board: board}
}
func (h *MessageBoardIndexHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["MsgBoardTitle"], engine.GR)
h.board.mu.Lock()
snap := make([]message, len(h.board.messages))
copy(snap, h.board.messages)
h.board.mu.Unlock()
if len(snap) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["MsgNoMessages"], engine.R))
} else {
start := 0
if len(snap) > 30 {
start = len(snap) - 30
}
for i, msg := range snap[start:] {
s.Printer.Send(fmt.Sprintf(" %s%02d%s %s%s%s %s%s:%s %s\r\n",
engine.GR, i+1, engine.R,
engine.GY, msg.Timestamp, engine.R,
engine.WH, msg.User, engine.R, msg.Text))
}
}
s.Printer.Pause(s.Lang)
}

View File

@@ -0,0 +1,38 @@
package content
import (
"bbs-server/engine"
"fmt"
"strings"
"time"
)
// MessageBoardNewHandler handles posting new messages to the board
type MessageBoardNewHandler struct {
board *MessageBoard
}
func NewMessageBoardNewHandler(board *MessageBoard) *MessageBoardNewHandler {
return &MessageBoardNewHandler{board: board}
}
func (h *MessageBoardNewHandler) Handle(s *engine.Session) {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.WH, s.Lang["MsgEnterText"], engine.R))
msgText, _ := s.Printer.ReadLine()
msgText = strings.TrimSpace(msgText)
if msgText != "" {
if len(msgText) > 200 {
msgText = msgText[:200]
}
ts := time.Now().Format("01-02 15:04")
msg := message{Timestamp: ts, User: s.Username, Text: msgText}
h.board.mu.Lock()
h.board.messages = append(h.board.messages, msg)
h.board.mu.Unlock()
h.board.append(msg)
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GR, s.Lang["MsgSent"], engine.R))
} else {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GY, s.Lang["MsgEmpty"], engine.R))
}
s.Printer.Pause(s.Lang)
}

View File

@@ -6,8 +6,14 @@ import (
"sort"
)
// Online displays currently logged-in users
func Online(s *engine.Session) {
// OnlineHandler displays currently logged-in users
type OnlineHandler struct{}
func NewOnlineHandler() *OnlineHandler {
return &OnlineHandler{}
}
func (h *OnlineHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["OnlineTitle"], engine.CY)
snap := s.State.Snapshot()

View File

@@ -0,0 +1,27 @@
package content
import (
"bbs-server/engine"
"fmt"
"runtime"
)
// SysinfoHandler displays system information
type SysinfoHandler struct {
board *MessageBoard
}
func NewSysinfoHandler(board *MessageBoard) *SysinfoHandler {
return &SysinfoHandler{board: board}
}
func (h *SysinfoHandler) Handle(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["SysTitle"], engine.GY)
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %d\r\n", engine.GY, s.Lang["SysUsers"], engine.WH, s.State.UserCount()))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %d\r\n", engine.GY, s.Lang["SysMessages"], engine.WH, h.board.Count()))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %s\r\n", engine.GY, s.Lang["SysOS"], engine.WH, runtime.GOOS))
s.Printer.Send(fmt.Sprintf(" %s%-15s%s %s\r\n", engine.GY, s.Lang["SysArch"], engine.WH, runtime.GOARCH))
s.Printer.Pause(s.Lang)
}

View File

@@ -1,121 +0,0 @@
package content
import (
"bbs-server/engine"
"encoding/csv"
"fmt"
"os"
"strings"
"sync"
"time"
)
type message struct {
Timestamp string
User string
Text string
}
// MessageBoard holds message board data and its handler
type MessageBoard struct {
messages []message
mu sync.Mutex
path string
}
// NewMessageBoard loads messages from the given .dat file (if it exists)
func NewMessageBoard(path string) *MessageBoard {
b := &MessageBoard{path: path}
b.load()
return b
}
func (b *MessageBoard) load() {
f, err := os.Open(b.path)
if err != nil {
return // file does not exist yet, start empty
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return
}
for _, row := range records {
if len(row) != 3 {
continue
}
b.messages = append(b.messages, message{Timestamp: row[0], User: row[1], Text: row[2]})
}
}
func (b *MessageBoard) append(msg message) error {
f, err := os.OpenFile(b.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
w := csv.NewWriter(f)
err = w.Write([]string{msg.Timestamp, msg.User, msg.Text})
w.Flush()
return err
}
// Count returns the number of messages in a thread-safe manner
func (b *MessageBoard) Count() int {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.messages)
}
// Show displays the message board and handles new message input
func (b *MessageBoard) Show(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["MsgBoardTitle"], engine.GR)
b.mu.Lock()
snap := make([]message, len(b.messages))
copy(snap, b.messages)
b.mu.Unlock()
if len(snap) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["MsgNoMessages"], engine.R))
} else {
start := 0
if len(snap) > 30 {
start = len(snap) - 30
}
for i, msg := range snap[start:] {
s.Printer.Send(fmt.Sprintf(" %s%02d%s %s%s%s %s%s:%s %s\r\n",
engine.GR, i+1, engine.R,
engine.GY, msg.Timestamp, engine.R,
engine.WH, msg.User, engine.R, msg.Text))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s[N]%s %s %s[ENTER]%s %s → ",
engine.GY, engine.R, s.Lang["MsgNew"],
engine.GY, engine.R, s.Lang["MsgBack"]))
choice, _ := s.Printer.ReadLine()
if strings.ToUpper(strings.TrimSpace(choice)) == "N" {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.WH, s.Lang["MsgEnterText"], engine.R))
msgText, _ := s.Printer.ReadLine()
msgText = strings.TrimSpace(msgText)
if msgText != "" {
if len(msgText) > 200 {
msgText = msgText[:200]
}
ts := time.Now().Format("01-02 15:04")
msg := message{Timestamp: ts, User: s.Username, Text: msgText}
b.mu.Lock()
b.messages = append(b.messages, msg)
b.mu.Unlock()
b.append(msg)
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GR, s.Lang["MsgSent"], engine.R))
} else {
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s\r\n", engine.GY, s.Lang["MsgEmpty"], engine.R))
}
}
}

View File

@@ -1,32 +0,0 @@
package content
import (
"bbs-server/engine"
"fmt"
"strconv"
"time"
)
// Sysinfo returns a HandlerFunc that displays system information
func Sysinfo(board *MessageBoard) engine.HandlerFunc {
return func(s *engine.Session) {
s.Printer.BoxHeader(s.Lang["SysInfoTitle"], engine.GY)
now := time.Now().Format("2006-01-02 15:04:05")
rows := [][]string{
{s.Lang["SysServerTime"], now},
{s.Lang["SysOnlineUsers"], strconv.Itoa(s.State.UserCount())},
{s.Lang["SysMsgCount"], strconv.Itoa(board.Count())},
{s.Lang["SysWikiURL"], WikiJSBaseURL},
{s.Lang["SysGamesAPI"], GamesAPIURL},
{s.Lang["SysPlatform"], "Go BBS v2.0"},
}
for _, row := range rows {
s.Printer.Send(fmt.Sprintf(" %s%-18s%s %s%s%s\r\n",
engine.GY, row[0], engine.R, engine.WH, row[1], engine.R))
}
s.Printer.Pause(s.Lang)
}
}

View File

@@ -1,203 +0,0 @@
package content
import (
"bbs-server/engine"
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
const WikiJSBaseURL = "https://wiki.teletype.hu"
type wikiPage struct {
ID interface{} `json:"id"`
Path string `json:"path"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Locale string `json:"locale"`
}
type wikiRepository struct {
token string
}
func (r *wikiRepository) graphql(query string) (map[string]interface{}, error) {
payload := map[string]string{"query": query}
data, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", WikiJSBaseURL+"/graphql", bytes.NewBuffer(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if r.token != "" {
req.Header.Set("Authorization", "Bearer "+r.token)
}
client := &http.Client{Timeout: 12 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func (r *wikiRepository) fetchList(tag string) ([]wikiPage, error) {
q := fmt.Sprintf(`{ pages { list(orderBy: CREATED, orderByDirection: DESC, tags: ["%s"]) { id path title description createdAt updatedAt locale } } }`, tag)
res, err := r.graphql(q)
if err != nil {
return nil, err
}
data, ok := res["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
pages, ok := data["pages"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
listRaw, ok := pages["list"].([]interface{})
if !ok {
return nil, fmt.Errorf("invalid response format")
}
var list []wikiPage
jsonBytes, _ := json.Marshal(listRaw)
json.Unmarshal(jsonBytes, &list)
return list, nil
}
func (r *wikiRepository) fetchContent(pageID interface{}) (string, error) {
var idStr string
switch v := pageID.(type) {
case string:
idStr = v
case float64:
idStr = fmt.Sprintf("%.0f", v)
default:
idStr = fmt.Sprintf("%v", v)
}
q := fmt.Sprintf(`{ pages { single(id: %s) { content } } }`, idStr)
res, err := r.graphql(q)
if err != nil {
return "", err
}
data := res["data"].(map[string]interface{})
pages := data["pages"].(map[string]interface{})
single := pages["single"].(map[string]interface{})
return single["content"].(string), nil
}
// WikiHandler renders Wiki.js content in the BBS
type WikiHandler struct {
repo *wikiRepository
}
func NewWikiHandler(token string) *WikiHandler {
return &WikiHandler{repo: &wikiRepository{token: token}}
}
// List returns a HandlerFunc that lists pages with the given tag
func (h *WikiHandler) List(tag, title, color string) engine.HandlerFunc {
return func(s *engine.Session) {
s.Printer.BoxHeader(title, color)
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, s.Lang["WikiLoading"], engine.R))
pages, err := h.repo.fetchList(tag)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%s%s: %v%s\r\n", engine.RD, s.Lang["WikiConnError"], err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
if len(pages) == 0 {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, fmt.Sprintf(s.Lang["WikiNoResults"], tag), engine.R))
s.Printer.Pause(s.Lang)
return
}
maxIdx := 25
if len(pages) < maxIdx {
maxIdx = len(pages)
}
for i, p := range pages[:maxIdx] {
date := s.Printer.FmtDate(p.CreatedAt)
if date == "" {
date = s.Printer.FmtDate(p.UpdatedAt)
}
titleP := p.Title
if titleP == "" {
titleP = p.Path
}
if len(titleP) > 55 {
titleP = titleP[:55]
}
desc := p.Description
if len(desc) > 60 {
desc = desc[:60]
}
s.Printer.Send(fmt.Sprintf(" %s%2d%s %s%s%s\r\n", color, i+1, engine.R, engine.WH, titleP, engine.R))
if desc != "" {
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, date, engine.DIM, desc, engine.R))
} else {
s.Printer.Send(fmt.Sprintf(" %s%s%s\r\n", engine.GY, date, engine.R))
}
}
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s ", engine.GY, s.Lang["WikiEnterNum"], engine.R))
choice, _ := s.Printer.ReadLine()
choice = strings.TrimSpace(choice)
idx, err := strconv.Atoi(choice)
if err != nil || idx < 1 || idx > len(pages) {
return
}
page := pages[idx-1]
s.Printer.Send(fmt.Sprintf("\r\n%s%s%s", engine.GY, s.Lang["WikiFetchContent"], engine.R))
pageContent, err := h.repo.fetchContent(page.ID)
if err != nil {
s.Printer.Send(fmt.Sprintf("\r\n%sHiba: %v%s\r\n", engine.RD, err, engine.R))
s.Printer.Pause(s.Lang)
return
}
s.Printer.Send("\r\033[A\033[2K")
s.Printer.Send("\r\n")
s.Printer.HR("═", color)
s.Printer.Send("\r\n")
s.Printer.Send(fmt.Sprintf(" %s%s%s%s\r\n", engine.WH, engine.B, page.Title, engine.R))
url := fmt.Sprintf("%s/%s/%s", WikiJSBaseURL, page.Locale, page.Path)
s.Printer.Send(fmt.Sprintf(" %s%s %s%s%s\r\n", engine.GY, s.Printer.FmtDate(page.CreatedAt), engine.DIM, url, engine.R))
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n\r\n")
body := s.Printer.Wrapped(pageContent, 2, 5000)
for _, line := range strings.Split(body, "\r\n") {
s.Printer.Send(line + "\r\n")
}
s.Printer.Send("\r\n")
s.Printer.HR("─", engine.GY)
s.Printer.Send("\r\n")
s.Printer.Pause(s.Lang)
}
}

26
main.go
View File

@@ -22,7 +22,7 @@ const banner = `
╚██████╔╝██║ ██║██║ ╚═╝ ██║███████╗███████║
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝
░░ BBS v2.0 ░░ teletype.hu ░░
░░ BBS v2.0 ░░ games.teletype.hu ░░
Welcome to the Teletype community bulletin board!
`
@@ -42,9 +42,14 @@ func main() {
fmt.Printf("Games API: %s\n", content.GamesAPIURL)
fmt.Printf("Token: %s\n", tokenStatus)
wiki := content.NewWikiHandler(wikiToken)
cat := content.NewCatalogHandler()
board := content.NewMessageBoard(boardPath)
messageBoard := content.NewMessageBoard(boardPath)
blogHandler := content.NewBlogHandler(wikiToken)
howtoHandler := content.NewHowToHandler(wikiToken)
catalogHandler := content.NewCatalogHandler()
messageBoardIndexHandler := content.NewMessageBoardIndexHandler(messageBoard)
messageBoardNewHandler := content.NewMessageBoardNewHandler(messageBoard)
onlineHandler := content.NewOnlineHandler()
sysinfoHandler := content.NewSysinfoHandler(messageBoard)
bbs := engine.New(engine.Config{
Host: "0.0.0.0",
@@ -55,12 +60,13 @@ func main() {
bbs.Menu(func(m *engine.Menu) {
m.Title("MAIN MENU")
m.Item("1", "Message Board", engine.GR, board.Show)
m.Item("2", "Blog Posts", engine.BL, wiki.List("blog", "Blog Posts", engine.BL))
m.Item("3", "HowTo Guides", engine.MG, wiki.List("howto", "HowTo Guides", engine.MG))
m.Item("4", "Game Catalog", engine.YL, cat.Show)
m.Item("5", "Online Users", engine.CY, content.Online)
m.Item("6", "System Info", engine.GY, content.Sysinfo(board))
m.Item("1", "Message Board", engine.GR, messageBoardIndexHandler.Handle)
m.Item("N", "New Message", engine.WH, messageBoardNewHandler.Handle)
m.Item("2", "Blog Posts", engine.BL, blogHandler.Handle)
m.Item("3", "HowTo Guides", engine.MG, howtoHandler.Handle)
m.Item("4", "Game Catalog", engine.YL, catalogHandler.Handle)
m.Item("5", "Online Users", engine.CY, onlineHandler.Handle)
m.Item("6", "System Info", engine.GY, sysinfoHandler.Handle)
m.Item("Q", "Exit", engine.RD, engine.Exit)
})