From 6856b073f60c2a0d9d7453205312c1640888f5a7 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Thu, 30 Apr 2026 09:37:57 +0200 Subject: [PATCH] Introduce model/ and repository/ structure under lib/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Models: Message, WikiPage, Game (typed structs instead of raw hashes) Repositories: MessageBoard, OnlineUsers, WikiClient, CatalogClient bbs.rb uses attribute access (page.title, game.play_path, …) throughout Co-Authored-By: Claude Sonnet 4.6 --- bbs.rb | 54 ++++++++++++--------------- lib/model/game.rb | 21 +++++++++++ lib/model/message.rb | 3 ++ lib/model/wiki_page.rb | 3 ++ lib/{ => repository}/catalog.rb | 6 ++- lib/{ => repository}/message_board.rb | 7 +--- lib/{ => repository}/online_users.rb | 0 lib/{ => repository}/wiki.rb | 12 +++++- 8 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 lib/model/game.rb create mode 100644 lib/model/message.rb create mode 100644 lib/model/wiki_page.rb rename lib/{ => repository}/catalog.rb (70%) rename lib/{ => repository}/message_board.rb (85%) rename lib/{ => repository}/online_users.rb (100%) rename lib/{ => repository}/wiki.rb (77%) diff --git a/bbs.rb b/bbs.rb index 6a5d887..857ef5d 100644 --- a/bbs.rb +++ b/bbs.rb @@ -2,10 +2,10 @@ require 'bbs' require 'time' -require_relative 'lib/online_users' -require_relative 'lib/message_board' -require_relative 'lib/wiki' -require_relative 'lib/catalog' +require_relative 'lib/repository/online_users' +require_relative 'lib/repository/message_board' +require_relative 'lib/repository/wiki' +require_relative 'lib/repository/catalog' ONLINE = OnlineUsers.new MESSAGES = MessageBoard.new(ENV.fetch('MESSAGES_PATH', 'data/messages.dat')) @@ -84,14 +84,14 @@ BBS.configure do |c| pick from: :pages, empty: 'No results.', prompt: 'Enter number to read (blank to go back)', - item: ->(p, i) { "#{C::GRAY}#{i}.#{C::RESET} #{C::BLUE}#{p['title']}#{C::RESET} #{C::GRAY}#{fmt_date(p['createdAt'])}#{C::RESET}" }, - hint: ->(p, i) { p['description'].to_s.strip[0...65] } do + item: ->(p, i) { "#{C::GRAY}#{i}.#{C::RESET} #{C::BLUE}#{p.title}#{C::RESET} #{C::GRAY}#{fmt_date(p.created_at)}#{C::RESET}" }, + hint: ->(p, i) { p.description.to_s.strip[0...65] } do fetch :page_body, loading: 'Loading page...' do |ctx| - WIKI.content(ctx[:picked]['id']) + WIKI.content(ctx[:picked].id) end line color: :blue - text(style: :white) { |ctx| ctx[:picked]['title'] } - text(style: :muted) { |ctx| "#{fmt_date(ctx[:picked]['createdAt'])} #{WIKI.page_url(ctx[:picked]['locale'], ctx[:picked]['path'])}" } + text(style: :white) { |ctx| ctx[:picked].title } + text(style: :muted) { |ctx| "#{fmt_date(ctx[:picked].created_at)} #{WIKI.page_url(ctx[:picked].locale, ctx[:picked].path)}" } line color: :blue body { |ctx| ctx[:page_body] } wait_enter @@ -106,14 +106,14 @@ BBS.configure do |c| pick from: :pages, empty: 'No results.', prompt: 'Enter number to read (blank to go back)', - item: ->(p, i) { "#{C::GRAY}#{i}.#{C::RESET} #{C::MAGENTA}#{p['title']}#{C::RESET} #{C::GRAY}#{fmt_date(p['createdAt'])}#{C::RESET}" }, - hint: ->(p, i) { p['description'].to_s.strip[0...65] } do + item: ->(p, i) { "#{C::GRAY}#{i}.#{C::RESET} #{C::MAGENTA}#{p.title}#{C::RESET} #{C::GRAY}#{fmt_date(p.created_at)}#{C::RESET}" }, + hint: ->(p, i) { p.description.to_s.strip[0...65] } do fetch :page_body, loading: 'Loading page...' do |ctx| - WIKI.content(ctx[:picked]['id']) + WIKI.content(ctx[:picked].id) end line color: :magenta - text(style: :white) { |ctx| ctx[:picked]['title'] } - text(style: :muted) { |ctx| "#{fmt_date(ctx[:picked]['createdAt'])} #{WIKI.page_url(ctx[:picked]['locale'], ctx[:picked]['path'])}" } + text(style: :white) { |ctx| ctx[:picked].title } + text(style: :muted) { |ctx| "#{fmt_date(ctx[:picked].created_at)} #{WIKI.page_url(ctx[:picked].locale, ctx[:picked].path)}" } line color: :magenta body { |ctx| ctx[:page_body] } wait_enter @@ -130,26 +130,20 @@ BBS.configure do |c| next " #{C::RED}Could not load catalog.#{C::RESET}\r\n\r\n" unless games.any? out = +'' - games.each_with_index do |entry, i| - next unless entry.is_a?(Hash) - sw = entry['software'] || {} - latest = entry['latestRelease'] || {} - count = (entry['releases'] || []).length - desc = sw['desc'].to_s.strip - + games.each_with_index do |game, i| out << "\r\n #{C::GRAY}#{'─' * 66}#{C::RESET}\r\n" - out << " #{C::CYAN}#{i + 1}. #{sw['title']}#{C::RESET} " \ - "#{C::GRAY}#{sw['platform']} #{sw['author']}#{C::RESET}\r\n" - out << wrap_text(desc) unless desc.empty? + out << " #{C::CYAN}#{i + 1}. #{game.title}#{C::RESET} " \ + "#{C::GRAY}#{game.platform} #{game.author}#{C::RESET}\r\n" + out << wrap_text(game.desc) unless game.desc.empty? badges = [] - badges << "#{C::GREEN}[▶ Play]#{C::RESET}" if latest['htmlFolderPath'].to_s != '' - badges << "#{C::YELLOW}[⬇ Download]#{C::RESET}" if latest['cartridgePath'].to_s != '' - badges << "#{C::BLUE}[Source]#{C::RESET}" if latest['sourcePath'].to_s != '' - badges << "#{C::MAGENTA}[Docs]#{C::RESET}" if latest['docsFolderPath'].to_s != '' + badges << "#{C::GREEN}[▶ Play]#{C::RESET}" unless game.play_path.empty? + badges << "#{C::YELLOW}[⬇ Download]#{C::RESET}" unless game.download_path.empty? + badges << "#{C::BLUE}[Source]#{C::RESET}" unless game.source_path.empty? + badges << "#{C::MAGENTA}[Docs]#{C::RESET}" unless game.docs_path.empty? out << " #{badges.join(' ')}\r\n" unless badges.empty? - out << " #{C::GRAY}#{CATALOG.play_url(latest['htmlFolderPath'])}#{C::RESET}\r\n" if latest['htmlFolderPath'].to_s != '' - out << " #{C::GRAY}#{count} versions available#{C::RESET}\r\n" if count > 1 + out << " #{C::GRAY}#{CATALOG.play_url(game.play_path)}#{C::RESET}\r\n" unless game.play_path.empty? + out << " #{C::GRAY}#{game.release_count} versions available#{C::RESET}\r\n" if game.release_count > 1 end out << "\r\n #{C::GRAY}#{'─' * 66}#{C::RESET}\r\n" diff --git a/lib/model/game.rb b/lib/model/game.rb new file mode 100644 index 0000000..7eb7e3f --- /dev/null +++ b/lib/model/game.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Game + attr_reader :title, :platform, :author, :desc, + :play_path, :download_path, :source_path, :docs_path, + :release_count + + def initialize(entry) + sw = entry['software'] || {} + latest = entry['latestRelease'] || {} + @title = sw['title'].to_s + @platform = sw['platform'].to_s + @author = sw['author'].to_s + @desc = sw['desc'].to_s + @play_path = latest['htmlFolderPath'].to_s + @download_path = latest['cartridgePath'].to_s + @source_path = latest['sourcePath'].to_s + @docs_path = latest['docsFolderPath'].to_s + @release_count = (entry['releases'] || []).length + end +end diff --git a/lib/model/message.rb b/lib/model/message.rb new file mode 100644 index 0000000..d3251ac --- /dev/null +++ b/lib/model/message.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +Message = Struct.new(:timestamp, :username, :text) diff --git a/lib/model/wiki_page.rb b/lib/model/wiki_page.rb new file mode 100644 index 0000000..301f4de --- /dev/null +++ b/lib/model/wiki_page.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +WikiPage = Struct.new(:id, :path, :title, :description, :created_at, :locale, keyword_init: true) diff --git a/lib/catalog.rb b/lib/repository/catalog.rb similarity index 70% rename from lib/catalog.rb rename to lib/repository/catalog.rb index bb2c6e9..627306f 100644 --- a/lib/catalog.rb +++ b/lib/repository/catalog.rb @@ -3,6 +3,7 @@ require 'net/http' require 'json' require 'uri' +require_relative '../model/game' class CatalogClient API_URL = 'https://games.teletype.hu/api/software' @@ -14,8 +15,9 @@ class CatalogClient http.use_ssl = uri.scheme == 'https' http.open_timeout = 12 http.read_timeout = 12 - data = JSON.parse(http.get(uri.path).body) - data.is_a?(Hash) ? (data['softwares'] || []) : data + data = JSON.parse(http.get(uri.path).body) + entries = data.is_a?(Hash) ? (data['softwares'] || []) : data + entries.filter_map { |e| Game.new(e) if e.is_a?(Hash) } rescue => e warn "Catalog fetch error: #{e}" [] diff --git a/lib/message_board.rb b/lib/repository/message_board.rb similarity index 85% rename from lib/message_board.rb rename to lib/repository/message_board.rb index 1420e75..9976c09 100644 --- a/lib/message_board.rb +++ b/lib/repository/message_board.rb @@ -3,10 +3,9 @@ require 'csv' require 'time' require 'fileutils' +require_relative '../model/message' class MessageBoard - Message = Struct.new(:timestamp, :username, :text) - def initialize(path) @path = path @messages = [] @@ -36,9 +35,7 @@ class MessageBoard def load_csv return unless File.exist?(@path) - CSV.foreach(@path) do |row| - @messages << Message.new(*row) - end + CSV.foreach(@path) { |row| @messages << Message.new(*row) } rescue => e warn "MessageBoard load error: #{e}" end diff --git a/lib/online_users.rb b/lib/repository/online_users.rb similarity index 100% rename from lib/online_users.rb rename to lib/repository/online_users.rb diff --git a/lib/wiki.rb b/lib/repository/wiki.rb similarity index 77% rename from lib/wiki.rb rename to lib/repository/wiki.rb index a3cc75a..c1bf54a 100644 --- a/lib/wiki.rb +++ b/lib/repository/wiki.rb @@ -3,6 +3,7 @@ require 'net/http' require 'json' require 'uri' +require_relative '../model/wiki_page' class WikiClient BASE_URL = 'https://wiki.teletype.hu' @@ -17,7 +18,16 @@ class WikiClient id path title description createdAt locale }}} GQL - graphql(query).dig('data', 'pages', 'list') || [] + (graphql(query).dig('data', 'pages', 'list') || []).map do |p| + WikiPage.new( + id: p['id'], + path: p['path'], + title: p['title'], + description: p['description'], + created_at: p['createdAt'], + locale: p['locale'] + ) + end rescue => e warn "Wiki list error: #{e}" []