Compare commits
11 Commits
5dfc2ed5c9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4244a2cc12 | |||
| 3a90dd541e | |||
| 304c130fa9 | |||
| ac77420888 | |||
| bafd7e1f25 | |||
| ac56b4974c | |||
| 78184ad62f | |||
| 390142d892 | |||
| 9ab2e09b3f | |||
| 6856b073f6 | |||
| 75d1063572 |
@@ -7,11 +7,11 @@ WORKDIR /app
|
|||||||
COPY Gemfile Gemfile.lock ./
|
COPY Gemfile Gemfile.lock ./
|
||||||
RUN bundle install
|
RUN bundle install
|
||||||
|
|
||||||
COPY bbs.rb ./
|
COPY bbs.rb entrypoint.sh ./
|
||||||
COPY lib/ ./lib/
|
COPY lib/ ./lib/
|
||||||
|
|
||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/data && chmod +x entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 2323
|
EXPOSE 2323
|
||||||
|
|
||||||
CMD ["bundle", "exec", "ruby", "bbs.rb"]
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
GIT
|
GIT
|
||||||
remote: https://git.teletype.hu/tools/rubbs.git
|
remote: https://git.teletype.hu/tools/rubbs.git
|
||||||
revision: d9232eabb4ddba2b860921657cf20fe7f3cbd830
|
revision: c01159dc2306ed9adda3b865346ae1f987649a67
|
||||||
specs:
|
specs:
|
||||||
bbs (0.1.0)
|
bbs (0.1.0)
|
||||||
artii (~> 2.1)
|
artii (~> 2.1)
|
||||||
|
|||||||
191
bbs.rb
191
bbs.rb
@@ -1,16 +1,82 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'bbs'
|
require 'bbs'
|
||||||
require_relative 'lib/online_users'
|
require 'time'
|
||||||
require_relative 'lib/message_board'
|
require_relative 'lib/repository/online_users_repository'
|
||||||
require_relative 'lib/wiki'
|
require_relative 'lib/repository/message_board_repository'
|
||||||
require_relative 'lib/catalog'
|
require_relative 'lib/repository/wiki_repository'
|
||||||
require_relative 'lib/display'
|
require_relative 'lib/repository/catalog_repository'
|
||||||
|
|
||||||
ONLINE = OnlineUsers.new
|
include BBS::Color
|
||||||
MESSAGES = MessageBoard.new(ENV.fetch('MESSAGES_PATH', 'data/messages.dat'))
|
|
||||||
WIKI = WikiClient.new(token: ENV['WEBAPP_WIKIJS_TOKEN'])
|
ONLINE = OnlineUsersRepository.new
|
||||||
CATALOG = CatalogClient.new
|
MESSAGES = MessageBoardRepository.new(ENV.fetch('MESSAGES_PATH', 'data/messages.dat'))
|
||||||
|
WIKI = WikiRepository.new(token: ENV['WEBAPP_WIKIJS_TOKEN'])
|
||||||
|
CATALOG = CatalogRepository.new
|
||||||
|
|
||||||
|
class OutputBuilder
|
||||||
|
def initialize = (@buf = +''; @badges = [])
|
||||||
|
def to_s = (flush_badges; @buf)
|
||||||
|
|
||||||
|
def sep(color = :gray)
|
||||||
|
flush_badges
|
||||||
|
@buf << "\r\n #{c(color, '─' * 66)}\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def line(text, color = nil)
|
||||||
|
flush_badges
|
||||||
|
@buf << " #{color ? c(color, text) : text}\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cols(**pairs)
|
||||||
|
flush_badges
|
||||||
|
@buf << " #{pairs.map { |color, text| c(color, text.to_s) }.join(' ')}\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def para(text)
|
||||||
|
flush_badges
|
||||||
|
@buf << wrap_text(text) unless text.to_s.strip.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def badge(color, text)
|
||||||
|
@badges << c(color, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def flush_badges
|
||||||
|
return if @badges.empty?
|
||||||
|
@buf << " #{@badges.join(' ')}\r\n"
|
||||||
|
@badges = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(&block)
|
||||||
|
OutputBuilder.new.tap { |b| b.instance_eval(&block) }.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def fmt_date(iso)
|
||||||
|
Time.parse(iso.to_s).strftime('%Y-%m-%d')
|
||||||
|
rescue
|
||||||
|
iso.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def wrap_text(text, width: 60, indent: 4)
|
||||||
|
clean = text.to_s
|
||||||
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
||||||
|
.gsub(/[#*_`~>|\\]/, '')
|
||||||
|
.gsub(/\r?\n+/, ' ')
|
||||||
|
.strip
|
||||||
|
lines, line = [], +''
|
||||||
|
clean.split.each do |word|
|
||||||
|
if line.empty? then line << word
|
||||||
|
elsif line.length + 1 + word.length <= width then line << ' ' << word
|
||||||
|
else lines << line.dup; line = +word
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lines << line unless line.empty?
|
||||||
|
lines.map { |l| "#{' ' * indent}#{l}\r\n" }.join
|
||||||
|
end
|
||||||
|
|
||||||
BBS.configure do |c|
|
BBS.configure do |c|
|
||||||
c.on_session_end = ->(session) { ONLINE.remove(session.session_id) }
|
c.on_session_end = ->(session) { ONLINE.remove(session.session_id) }
|
||||||
@@ -21,41 +87,122 @@ BBS.configure do |c|
|
|||||||
ask :username, prompt: 'Name (blank for Anonymous)',
|
ask :username, prompt: 'Name (blank for Anonymous)',
|
||||||
transform: ->(v) { v.strip.empty? ? 'Anonymous' : v.strip[0...20] }
|
transform: ->(v) { v.strip.empty? ? 'Anonymous' : v.strip[0...20] }
|
||||||
|
|
||||||
call do |ctx, runner|
|
call { |ctx| ONLINE.add(ctx[:session_id], ctx[:username]) }
|
||||||
ONLINE.add(runner.session_id, ctx[:username])
|
|
||||||
end
|
|
||||||
|
|
||||||
menu 'Choice', loop: true do
|
menu 'Choice', loop: true do
|
||||||
option 'Message Board' do
|
option 'Message Board' do
|
||||||
call { |ctx, runner| Display.render_messages(MESSAGES.last(30), runner) }
|
section 'Message Board', color: :cyan
|
||||||
|
rows(empty: 'No messages yet.') do |ctx|
|
||||||
|
MESSAGES.last(30).map.with_index(1) do |msg, i|
|
||||||
|
"#{c(:gray, "[#{i}]")} #{c(:yellow, msg.timestamp)} #{c(:white, msg.username)}: #{c(:gray, msg.text)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
wait_enter
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'New Message' do
|
option 'New Message' do
|
||||||
ask :message_text, prompt: 'Message (max 200 chars)', validate: :non_empty
|
ask :message_text, prompt: 'Message (max 200 chars)', validate: :non_empty
|
||||||
call do |ctx, runner|
|
call { |ctx| MESSAGES.append(ctx[:username], ctx[:message_text][0...200]) }
|
||||||
MESSAGES.append(ctx[:username], ctx[:message_text][0...200])
|
say 'Sent.', style: :success
|
||||||
runner.write "\r\n #{Display::GREEN}Sent.#{Display::RESET}\r\n\r\n"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'Blog Posts' do
|
option 'Blog Posts' do
|
||||||
call { |ctx, runner| Display.render_wiki_list(WIKI, 'blog', 'Blog Posts', Display::BLUE, runner) }
|
section 'Blog Posts', color: :blue
|
||||||
|
fetch :pages, loading: 'Loading...' do |ctx|
|
||||||
|
WIKI.list('blog')
|
||||||
|
end
|
||||||
|
pick from: :pages,
|
||||||
|
empty: 'No results.',
|
||||||
|
prompt: 'Enter number to read (blank to go back)',
|
||||||
|
item: ->(p, i) { "#{c(:gray, "#{i}.")} #{c(:blue, p.title)} #{c(:gray, fmt_date(p.created_at))}" },
|
||||||
|
hint: ->(p, i) { p.description.to_s.strip[0...65] } do
|
||||||
|
fetch :page_body, loading: 'Loading page...' do |ctx|
|
||||||
|
WIKI.content(ctx[:picked].id)
|
||||||
|
end
|
||||||
|
line color: :blue
|
||||||
|
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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'HowTo Guides' do
|
option 'HowTo Guides' do
|
||||||
call { |ctx, runner| Display.render_wiki_list(WIKI, 'howto', 'HowTo Guides', Display::MAGENTA, runner) }
|
section 'HowTo Guides', color: :magenta
|
||||||
|
fetch :pages, loading: 'Loading...' do |ctx|
|
||||||
|
WIKI.list('howto')
|
||||||
|
end
|
||||||
|
pick from: :pages,
|
||||||
|
empty: 'No results.',
|
||||||
|
prompt: 'Enter number to read (blank to go back)',
|
||||||
|
item: ->(p, i) { "#{c(:gray, "#{i}.")} #{c(:magenta, p.title)} #{c(:gray, fmt_date(p.created_at))}" },
|
||||||
|
hint: ->(p, i) { p.description.to_s.strip[0...65] } do
|
||||||
|
fetch :page_body, loading: 'Loading page...' do |ctx|
|
||||||
|
WIKI.content(ctx[:picked].id)
|
||||||
|
end
|
||||||
|
line color: :magenta
|
||||||
|
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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'Game Catalog' do
|
option 'Game Catalog' do
|
||||||
call { |ctx, runner| Display.render_catalog(CATALOG, runner) }
|
section 'Game Catalog', color: :cyan
|
||||||
|
fetch :games, loading: 'Loading...' do |ctx|
|
||||||
|
CATALOG.fetch
|
||||||
|
end
|
||||||
|
output do |ctx|
|
||||||
|
games = Array(ctx[:games])
|
||||||
|
next " #{c(:red, 'Could not load catalog.')}\r\n" unless games.any?
|
||||||
|
|
||||||
|
render do
|
||||||
|
games.each_with_index do |game, i|
|
||||||
|
sep
|
||||||
|
cols cyan: "#{i + 1}. #{game.title}", gray: "#{game.platform} #{game.author}"
|
||||||
|
para game.desc
|
||||||
|
badge :green, '[▶ Play]' unless game.play_path.empty?
|
||||||
|
badge :yellow, '[⬇ Download]' unless game.download_path.empty?
|
||||||
|
badge :blue, '[Source]' unless game.source_path.empty?
|
||||||
|
badge :magenta, '[Docs]' unless game.docs_path.empty?
|
||||||
|
line CATALOG.play_url(game.play_path), :gray unless game.play_path.empty?
|
||||||
|
line "#{game.release_count} versions available", :gray if game.release_count > 1
|
||||||
|
end
|
||||||
|
sep
|
||||||
|
line CatalogRepository::GAMES_URL, :gray
|
||||||
|
end
|
||||||
|
end
|
||||||
|
wait_enter
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'Online Users' do
|
option 'Online Users' do
|
||||||
call { |ctx, runner| Display.render_online(ONLINE, runner.session_id, ctx[:username], runner) }
|
section 'Online Users', color: :yellow
|
||||||
|
rows do |ctx|
|
||||||
|
ONLINE.snapshot.sort.map do |sid, name|
|
||||||
|
marker = sid == ctx[:session_id] ? " #{c(:gray, '← you')}" : ''
|
||||||
|
"#{c(:white, name)}#{marker}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
text(style: :muted) { |ctx| "#{ONLINE.count} user(s) online" }
|
||||||
|
wait_enter
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'System Info' do
|
option 'System Info' do
|
||||||
call { |ctx, runner| Display.render_sysinfo(ONLINE, MESSAGES, runner) }
|
section 'System Info', color: :magenta
|
||||||
|
table do |ctx|
|
||||||
|
{
|
||||||
|
'Online users' => ONLINE.count,
|
||||||
|
'Messages' => MESSAGES.count,
|
||||||
|
'Wiki' => 'https://wiki.teletype.hu',
|
||||||
|
'Games API' => 'https://games.teletype.hu',
|
||||||
|
'Platform' => RUBY_PLATFORM,
|
||||||
|
'Ruby' => RUBY_VERSION
|
||||||
|
}
|
||||||
|
end
|
||||||
|
wait_enter
|
||||||
end
|
end
|
||||||
|
|
||||||
option 'Exit' do
|
option 'Exit' do
|
||||||
|
|||||||
4
entrypoint.sh
Normal file
4
entrypoint.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
bundle update bbs
|
||||||
|
exec bundle exec ruby bbs.rb
|
||||||
201
lib/display.rb
201
lib/display.rb
@@ -1,201 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'time'
|
|
||||||
|
|
||||||
module Display
|
|
||||||
W = 70
|
|
||||||
|
|
||||||
RESET = "\e[0m"
|
|
||||||
BOLD = "\e[1m"
|
|
||||||
CYAN = "\e[0;36m"
|
|
||||||
YELLOW = "\e[0;33m"
|
|
||||||
GREEN = "\e[1;32m"
|
|
||||||
RED = "\e[1;31m"
|
|
||||||
MAGENTA = "\e[0;35m"
|
|
||||||
WHITE = "\e[1;37m"
|
|
||||||
BLUE = "\e[0;34m"
|
|
||||||
GRAY = "\e[0;37m"
|
|
||||||
|
|
||||||
module_function
|
|
||||||
|
|
||||||
def hr(color = GRAY)
|
|
||||||
" #{color}#{'─' * (W - 4)}#{RESET}\r\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def box_header(title, color = CYAN)
|
|
||||||
pad = [W - title.length - 6, 2].max
|
|
||||||
" #{color}┌─ #{WHITE}#{title} #{color}#{'─' * pad}┐#{RESET}\r\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def strip_md(text)
|
|
||||||
text.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
||||||
.gsub(/[#*_`~>|\\]/, '')
|
|
||||||
.gsub(/\r?\n+/, ' ')
|
|
||||||
.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def wrap(text, indent: 4, width: W - 6)
|
|
||||||
words = strip_md(text).split
|
|
||||||
lines = []
|
|
||||||
line = +''
|
|
||||||
words.each do |word|
|
|
||||||
if line.empty?
|
|
||||||
line << word
|
|
||||||
elsif line.length + 1 + word.length <= width
|
|
||||||
line << ' ' << word
|
|
||||||
else
|
|
||||||
lines << line.dup
|
|
||||||
line = +word
|
|
||||||
end
|
|
||||||
end
|
|
||||||
lines << line unless line.empty?
|
|
||||||
lines.map { |l| "#{' ' * indent}#{l}\r\n" }.join
|
|
||||||
end
|
|
||||||
|
|
||||||
def fmt_date(iso)
|
|
||||||
Time.parse(iso).strftime('%Y-%m-%d')
|
|
||||||
rescue
|
|
||||||
iso.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def wait_enter(runner)
|
|
||||||
runner.write "\r\n #{GRAY}Press ENTER to continue...#{RESET}"
|
|
||||||
runner.readline
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_messages(messages, runner)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write box_header('Message Board')
|
|
||||||
runner.write "\r\n"
|
|
||||||
if messages.empty?
|
|
||||||
runner.write " #{GRAY}No messages yet.#{RESET}\r\n"
|
|
||||||
else
|
|
||||||
messages.each_with_index do |msg, i|
|
|
||||||
runner.write " #{GRAY}[#{i + 1}]#{RESET} #{YELLOW}#{msg.timestamp}#{RESET} #{WHITE}#{msg.username}#{RESET}: #{GRAY}#{msg.text}#{RESET}\r\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
runner.write "\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_wiki_list(wiki, tag, title, color, runner)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write box_header(title, color)
|
|
||||||
runner.write "\r\n #{GRAY}Loading...#{RESET}\r\n"
|
|
||||||
|
|
||||||
pages = wiki.list(tag)
|
|
||||||
runner.write "\e[1A\e[2K"
|
|
||||||
|
|
||||||
if pages.empty?
|
|
||||||
runner.write " #{RED}No results.#{RESET}\r\n\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
shown = pages.first(25)
|
|
||||||
shown.each_with_index do |page, i|
|
|
||||||
runner.write " #{GRAY}#{i + 1}.#{RESET} #{color}#{page['title']}#{RESET} #{GRAY}#{fmt_date(page['createdAt'])}#{RESET}\r\n"
|
|
||||||
desc = page['description'].to_s.strip
|
|
||||||
runner.write " #{GRAY}#{desc[0...65]}#{RESET}\r\n" unless desc.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
runner.write "\r\n #{GRAY}Enter number to read (blank to go back):#{RESET} "
|
|
||||||
input = runner.readline&.strip
|
|
||||||
return if input.nil? || input.empty?
|
|
||||||
|
|
||||||
idx = input.to_i - 1
|
|
||||||
page = shown[idx]
|
|
||||||
unless page
|
|
||||||
runner.write "\r\n #{RED}Invalid selection.#{RESET}\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
runner.write "\r\n #{GRAY}Loading page...#{RESET}\r\n"
|
|
||||||
body = wiki.content(page['id'])
|
|
||||||
runner.write "\e[1A\e[2K"
|
|
||||||
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write hr(color)
|
|
||||||
runner.write " #{color}#{page['title']}#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}#{fmt_date(page['createdAt'])} #{wiki.page_url(page['locale'], page['path'])}#{RESET}\r\n"
|
|
||||||
runner.write hr(color)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write wrap(body)
|
|
||||||
runner.write "\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_catalog(catalog, runner)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write box_header('Game Catalog', CYAN)
|
|
||||||
runner.write "\r\n #{GRAY}Loading...#{RESET}\r\n"
|
|
||||||
|
|
||||||
games = catalog.fetch
|
|
||||||
runner.write "\e[1A\e[2K"
|
|
||||||
|
|
||||||
unless games.is_a?(Array) && !games.empty?
|
|
||||||
runner.write " #{RED}Could not load catalog.#{RESET}\r\n\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
games.each_with_index do |entry, i|
|
|
||||||
next unless entry.is_a?(Hash)
|
|
||||||
sw = entry['software'] || {}
|
|
||||||
latest = entry['latestRelease'] || {}
|
|
||||||
count = (entry['releases'] || []).length
|
|
||||||
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write hr
|
|
||||||
runner.write " #{CYAN}#{i + 1}. #{sw['title']}#{RESET} #{GRAY}#{sw['platform']} #{sw['author']}#{RESET}\r\n"
|
|
||||||
runner.write wrap(sw['desc'].to_s) unless sw['desc'].to_s.strip.empty?
|
|
||||||
|
|
||||||
badges = []
|
|
||||||
badges << "#{GREEN}[▶ Play]#{RESET}" if latest['htmlFolderPath'].to_s != ''
|
|
||||||
badges << "#{YELLOW}[⬇ Download]#{RESET}" if latest['cartridgePath'].to_s != ''
|
|
||||||
badges << "#{BLUE}[Source]#{RESET}" if latest['sourcePath'].to_s != ''
|
|
||||||
badges << "#{MAGENTA}[Docs]#{RESET}" if latest['docsFolderPath'].to_s != ''
|
|
||||||
runner.write " #{badges.join(' ')}\r\n" unless badges.empty?
|
|
||||||
|
|
||||||
if latest['htmlFolderPath'].to_s != ''
|
|
||||||
runner.write " #{GRAY}#{catalog.play_url(latest['htmlFolderPath'])}#{RESET}\r\n"
|
|
||||||
end
|
|
||||||
runner.write " #{GRAY}#{count} versions available#{RESET}\r\n" if count > 1
|
|
||||||
end
|
|
||||||
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write hr
|
|
||||||
runner.write " #{GRAY}#{CatalogClient::GAMES_URL}#{RESET}\r\n\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_online(online, my_sid, my_name, runner)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write box_header('Online Users', YELLOW)
|
|
||||||
runner.write "\r\n"
|
|
||||||
|
|
||||||
snapshot = online.snapshot
|
|
||||||
snapshot.sort.each do |sid, name|
|
|
||||||
marker = sid == my_sid ? " #{GRAY}← you#{RESET}" : ''
|
|
||||||
runner.write " #{WHITE}#{name}#{RESET}#{marker}\r\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
runner.write "\r\n #{GRAY}#{snapshot.size} user(s) online#{RESET}\r\n\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_sysinfo(online, messages, runner)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write box_header('System Info', MAGENTA)
|
|
||||||
runner.write "\r\n"
|
|
||||||
runner.write " #{GRAY}Online users #{WHITE}#{online.count}#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}Messages #{WHITE}#{messages.count}#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}Wiki #{WHITE}https://wiki.teletype.hu#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}Games API #{WHITE}https://games.teletype.hu#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}Platform #{WHITE}#{RUBY_PLATFORM}#{RESET}\r\n"
|
|
||||||
runner.write " #{GRAY}Ruby #{WHITE}#{RUBY_VERSION}#{RESET}\r\n"
|
|
||||||
runner.write "\r\n"
|
|
||||||
wait_enter(runner)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
21
lib/model/game_model.rb
Normal file
21
lib/model/game_model.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class GameModel
|
||||||
|
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
|
||||||
3
lib/model/message_model.rb
Normal file
3
lib/model/message_model.rb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
MessageModel = Struct.new(:timestamp, :username, :text)
|
||||||
3
lib/model/wiki_page_model.rb
Normal file
3
lib/model/wiki_page_model.rb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
WikiPageModel = Struct.new(:id, :path, :title, :description, :created_at, :locale, keyword_init: true)
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
require 'net/http'
|
require 'net/http'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require_relative '../model/game_model'
|
||||||
|
|
||||||
class CatalogClient
|
class CatalogRepository
|
||||||
API_URL = 'https://games.teletype.hu/api/software'
|
API_URL = 'https://games.teletype.hu/api/software'
|
||||||
GAMES_URL = 'https://games.teletype.hu'
|
GAMES_URL = 'https://games.teletype.hu'
|
||||||
|
|
||||||
@@ -15,9 +16,10 @@ class CatalogClient
|
|||||||
http.open_timeout = 12
|
http.open_timeout = 12
|
||||||
http.read_timeout = 12
|
http.read_timeout = 12
|
||||||
data = JSON.parse(http.get(uri.path).body)
|
data = JSON.parse(http.get(uri.path).body)
|
||||||
data.is_a?(Hash) ? (data['softwares'] || []) : data
|
entries = data.is_a?(Hash) ? (data['softwares'] || []) : data
|
||||||
|
entries.filter_map { |e| GameModel.new(e) if e.is_a?(Hash) }
|
||||||
rescue => e
|
rescue => e
|
||||||
warn "Catalog fetch error: #{e}"
|
warn "CatalogRepository fetch error: #{e}"
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
require 'csv'
|
require 'csv'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
|
require_relative '../model/message_model'
|
||||||
|
|
||||||
class MessageBoard
|
class MessageBoardRepository
|
||||||
Message = Struct.new(:timestamp, :username, :text)
|
|
||||||
|
|
||||||
def initialize(path)
|
def initialize(path)
|
||||||
@path = path
|
@path = path
|
||||||
@messages = []
|
@messages = []
|
||||||
@@ -15,7 +14,7 @@ class MessageBoard
|
|||||||
end
|
end
|
||||||
|
|
||||||
def append(username, text)
|
def append(username, text)
|
||||||
msg = Message.new(Time.now.strftime('%m-%d %H:%M'), username, text)
|
msg = MessageModel.new(Time.now.strftime('%m-%d %H:%M'), username, text)
|
||||||
@mu.synchronize do
|
@mu.synchronize do
|
||||||
@messages << msg
|
@messages << msg
|
||||||
FileUtils.mkdir_p(File.dirname(@path))
|
FileUtils.mkdir_p(File.dirname(@path))
|
||||||
@@ -36,10 +35,8 @@ class MessageBoard
|
|||||||
|
|
||||||
def load_csv
|
def load_csv
|
||||||
return unless File.exist?(@path)
|
return unless File.exist?(@path)
|
||||||
CSV.foreach(@path) do |row|
|
CSV.foreach(@path) { |row| @messages << MessageModel.new(*row) }
|
||||||
@messages << Message.new(*row)
|
|
||||||
end
|
|
||||||
rescue => e
|
rescue => e
|
||||||
warn "MessageBoard load error: #{e}"
|
warn "MessageBoardRepository load error: #{e}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class OnlineUsers
|
class OnlineUsersRepository
|
||||||
def initialize
|
def initialize
|
||||||
@users = {}
|
@users = {}
|
||||||
@mu = Mutex.new
|
@mu = Mutex.new
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
require 'net/http'
|
require 'net/http'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require_relative '../model/wiki_page_model'
|
||||||
|
|
||||||
class WikiClient
|
class WikiRepository
|
||||||
BASE_URL = 'https://wiki.teletype.hu'
|
BASE_URL = 'https://wiki.teletype.hu'
|
||||||
|
|
||||||
def initialize(token: nil)
|
def initialize(token: nil)
|
||||||
@@ -17,9 +18,18 @@ class WikiClient
|
|||||||
id path title description createdAt locale
|
id path title description createdAt locale
|
||||||
}}}
|
}}}
|
||||||
GQL
|
GQL
|
||||||
graphql(query).dig('data', 'pages', 'list') || []
|
(graphql(query).dig('data', 'pages', 'list') || []).map do |p|
|
||||||
|
WikiPageModel.new(
|
||||||
|
id: p['id'],
|
||||||
|
path: p['path'],
|
||||||
|
title: p['title'],
|
||||||
|
description: p['description'],
|
||||||
|
created_at: p['createdAt'],
|
||||||
|
locale: p['locale']
|
||||||
|
)
|
||||||
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
warn "Wiki list error: #{e}"
|
warn "WikiRepository list error: #{e}"
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -27,7 +37,7 @@ class WikiClient
|
|||||||
query = "{ pages { single(id: #{page_id}) { content } } }"
|
query = "{ pages { single(id: #{page_id}) { content } } }"
|
||||||
graphql(query).dig('data', 'pages', 'single', 'content') || ''
|
graphql(query).dig('data', 'pages', 'single', 'content') || ''
|
||||||
rescue => e
|
rescue => e
|
||||||
warn "Wiki content error: #{e}"
|
warn "WikiRepository content error: #{e}"
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
Reference in New Issue
Block a user