ruby version
This commit is contained in:
32
Dockerfile
32
Dockerfile
@@ -1,31 +1,17 @@
|
|||||||
# Build stage
|
FROM ruby:3.3-alpine
|
||||||
FROM golang:1.26.1-alpine AS builder
|
|
||||||
|
RUN apk add --no-cache git build-base
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
COPY Gemfile Gemfile.lock ./
|
||||||
COPY go.mod ./
|
RUN bundle install
|
||||||
# COPY go.sum ./ # Uncomment if you have a go.sum file
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
COPY bbs.rb ./
|
||||||
COPY . .
|
COPY lib/ ./lib/
|
||||||
|
|
||||||
# Build the application
|
RUN mkdir -p /app/data
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o bbs-server main.go
|
|
||||||
|
|
||||||
# Final stage
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
|
||||||
|
|
||||||
WORKDIR /root/
|
|
||||||
|
|
||||||
# Copy the binary from the builder stage
|
|
||||||
COPY --from=builder /app/bbs-server .
|
|
||||||
|
|
||||||
# Expose the port
|
|
||||||
EXPOSE 2323
|
EXPOSE 2323
|
||||||
|
|
||||||
# Run the binary
|
CMD ["bundle", "exec", "ruby", "bbs.rb"]
|
||||||
CMD ["./bbs-server"]
|
|
||||||
|
|||||||
3
Gemfile
Normal file
3
Gemfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem 'bbs', git: 'https://git.teletype.hu/tools/rubbs.git'
|
||||||
21
Gemfile.lock
Normal file
21
Gemfile.lock
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
GIT
|
||||||
|
remote: https://git.teletype.hu/tools/rubbs.git
|
||||||
|
revision: d9232eabb4ddba2b860921657cf20fe7f3cbd830
|
||||||
|
specs:
|
||||||
|
bbs (0.1.0)
|
||||||
|
artii (~> 2.1)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
artii (2.1.2)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
arm64-darwin-22
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
bbs!
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.5.9
|
||||||
70
bbs.rb
Normal file
70
bbs.rb
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'bbs'
|
||||||
|
require_relative 'lib/online_users'
|
||||||
|
require_relative 'lib/message_board'
|
||||||
|
require_relative 'lib/wiki'
|
||||||
|
require_relative 'lib/catalog'
|
||||||
|
require_relative 'lib/display'
|
||||||
|
|
||||||
|
ONLINE = OnlineUsers.new
|
||||||
|
MESSAGES = MessageBoard.new(ENV.fetch('MESSAGES_PATH', 'data/messages.dat'))
|
||||||
|
WIKI = WikiClient.new(token: ENV['WEBAPP_WIKIJS_TOKEN'])
|
||||||
|
CATALOG = CatalogClient.new
|
||||||
|
|
||||||
|
BBS.configure do |c|
|
||||||
|
c.on_session_end = ->(session) { ONLINE.remove(session.session_id) }
|
||||||
|
|
||||||
|
c.flow = BBS::Flow.define do
|
||||||
|
big_banner 'TELETYPE BBS', style: :success
|
||||||
|
|
||||||
|
ask :username, prompt: 'Name (blank for Anonymous)',
|
||||||
|
transform: ->(v) { v.strip.empty? ? 'Anonymous' : v.strip[0...20] }
|
||||||
|
|
||||||
|
call do |ctx, runner|
|
||||||
|
ONLINE.add(runner.session_id, ctx[:username])
|
||||||
|
end
|
||||||
|
|
||||||
|
menu 'Choice', loop: true do
|
||||||
|
option 'Message Board' do
|
||||||
|
call { |ctx, runner| Display.render_messages(MESSAGES.last(30), runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'New Message' do
|
||||||
|
ask :message_text, prompt: 'Message (max 200 chars)', validate: :non_empty
|
||||||
|
call do |ctx, runner|
|
||||||
|
MESSAGES.append(ctx[:username], ctx[:message_text][0...200])
|
||||||
|
runner.write "\r\n #{Display::GREEN}Sent.#{Display::RESET}\r\n\r\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'Blog Posts' do
|
||||||
|
call { |ctx, runner| Display.render_wiki_list(WIKI, 'blog', 'Blog Posts', Display::BLUE, runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'HowTo Guides' do
|
||||||
|
call { |ctx, runner| Display.render_wiki_list(WIKI, 'howto', 'HowTo Guides', Display::MAGENTA, runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'Game Catalog' do
|
||||||
|
call { |ctx, runner| Display.render_catalog(CATALOG, runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'Online Users' do
|
||||||
|
call { |ctx, runner| Display.render_online(ONLINE, runner.session_id, ctx[:username], runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'System Info' do
|
||||||
|
call { |ctx, runner| Display.render_sysinfo(ONLINE, MESSAGES, runner) }
|
||||||
|
end
|
||||||
|
|
||||||
|
option 'Exit' do
|
||||||
|
exit_menu
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
say 'Goodbye!', style: :muted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
BBS.start
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
services:
|
services:
|
||||||
bbs-server:
|
bbs-server:
|
||||||
build:
|
build: .
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: bbs-server-prod
|
container_name: bbs-server-prod
|
||||||
ports:
|
ports:
|
||||||
- "2323:2323"
|
- "2323:2323"
|
||||||
environment:
|
environment:
|
||||||
- WEBAPP_WIKIJS_TOKEN=${WEBAPP_WIKIJS_TOKEN}
|
- WEBAPP_WIKIJS_TOKEN=${WEBAPP_WIKIJS_TOKEN}
|
||||||
- MESSAGES_PATH=/data/messages.dat
|
- MESSAGES_PATH=/app/data/messages.dat
|
||||||
volumes:
|
volumes:
|
||||||
- bbs-messages:/data
|
- bbs-messages:/app/data
|
||||||
restart: always
|
restart: always
|
||||||
read_only: true
|
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /tmp
|
- /tmp
|
||||||
cap_drop:
|
cap_drop:
|
||||||
@@ -21,4 +18,4 @@ services:
|
|||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
bbs-messages:
|
bbs-messages:
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
services:
|
services:
|
||||||
bbs:
|
bbs:
|
||||||
build:
|
build: .
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.development
|
|
||||||
container_name: teletype-bbs
|
container_name: teletype-bbs
|
||||||
ports:
|
ports:
|
||||||
- "2323:2323"
|
- "2323:2323"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app/
|
- ./data:/app/data
|
||||||
- ./data:/data
|
|
||||||
environment:
|
environment:
|
||||||
- WEBAPP_WIKIJS_TOKEN=${WEBAPP_WIKIJS_TOKEN:-}
|
- WEBAPP_WIKIJS_TOKEN=${WEBAPP_WIKIJS_TOKEN:-}
|
||||||
- MESSAGES_PATH=/data/messages.dat
|
- MESSAGES_PATH=/app/data/messages.dat
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
WEBAPP_WIKIJS_TOKEN=
|
WEBAPP_WIKIJS_TOKEN=
|
||||||
|
MESSAGES_PATH=data/messages.dat
|
||||||
27
lib/catalog.rb
Normal file
27
lib/catalog.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'net/http'
|
||||||
|
require 'json'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
class CatalogClient
|
||||||
|
API_URL = 'https://games.teletype.hu/api/software'
|
||||||
|
GAMES_URL = 'https://games.teletype.hu'
|
||||||
|
|
||||||
|
def fetch
|
||||||
|
uri = URI(API_URL)
|
||||||
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
|
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
|
||||||
|
rescue => e
|
||||||
|
warn "Catalog fetch error: #{e}"
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def play_url(path)
|
||||||
|
"#{GAMES_URL}#{path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
201
lib/display.rb
Normal file
201
lib/display.rb
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# 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
|
||||||
45
lib/message_board.rb
Normal file
45
lib/message_board.rb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'csv'
|
||||||
|
require 'time'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
class MessageBoard
|
||||||
|
Message = Struct.new(:timestamp, :username, :text)
|
||||||
|
|
||||||
|
def initialize(path)
|
||||||
|
@path = path
|
||||||
|
@messages = []
|
||||||
|
@mu = Mutex.new
|
||||||
|
load_csv
|
||||||
|
end
|
||||||
|
|
||||||
|
def append(username, text)
|
||||||
|
msg = Message.new(Time.now.strftime('%m-%d %H:%M'), username, text)
|
||||||
|
@mu.synchronize do
|
||||||
|
@messages << msg
|
||||||
|
FileUtils.mkdir_p(File.dirname(@path))
|
||||||
|
CSV.open(@path, 'a') { |csv| csv << [msg.timestamp, msg.username, msg.text] }
|
||||||
|
end
|
||||||
|
msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def last(n = 30)
|
||||||
|
@mu.synchronize { @messages.last(n) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def count
|
||||||
|
@mu.synchronize { @messages.size }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_csv
|
||||||
|
return unless File.exist?(@path)
|
||||||
|
CSV.foreach(@path) do |row|
|
||||||
|
@messages << Message.new(*row)
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
warn "MessageBoard load error: #{e}"
|
||||||
|
end
|
||||||
|
end
|
||||||
24
lib/online_users.rb
Normal file
24
lib/online_users.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class OnlineUsers
|
||||||
|
def initialize
|
||||||
|
@users = {}
|
||||||
|
@mu = Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(session_id, name)
|
||||||
|
@mu.synchronize { @users[session_id] = name }
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove(session_id)
|
||||||
|
@mu.synchronize { @users.delete(session_id) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def snapshot
|
||||||
|
@mu.synchronize { @users.dup }
|
||||||
|
end
|
||||||
|
|
||||||
|
def count
|
||||||
|
@mu.synchronize { @users.size }
|
||||||
|
end
|
||||||
|
end
|
||||||
53
lib/wiki.rb
Normal file
53
lib/wiki.rb
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'net/http'
|
||||||
|
require 'json'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
class WikiClient
|
||||||
|
BASE_URL = 'https://wiki.teletype.hu'
|
||||||
|
|
||||||
|
def initialize(token: nil)
|
||||||
|
@token = token
|
||||||
|
end
|
||||||
|
|
||||||
|
def list(tag)
|
||||||
|
query = <<~GQL
|
||||||
|
{ pages { list(orderBy: CREATED, orderByDirection: DESC, tags: ["#{tag}"]) {
|
||||||
|
id path title description createdAt locale
|
||||||
|
}}}
|
||||||
|
GQL
|
||||||
|
graphql(query).dig('data', 'pages', 'list') || []
|
||||||
|
rescue => e
|
||||||
|
warn "Wiki list error: #{e}"
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def content(page_id)
|
||||||
|
query = "{ pages { single(id: #{page_id}) { content } } }"
|
||||||
|
graphql(query).dig('data', 'pages', 'single', 'content') || ''
|
||||||
|
rescue => e
|
||||||
|
warn "Wiki content error: #{e}"
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
|
def page_url(locale, path)
|
||||||
|
"#{BASE_URL}/#{locale}/#{path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def graphql(query)
|
||||||
|
uri = URI("#{BASE_URL}/graphql")
|
||||||
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
|
http.use_ssl = uri.scheme == 'https'
|
||||||
|
http.open_timeout = 12
|
||||||
|
http.read_timeout = 12
|
||||||
|
|
||||||
|
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
||||||
|
req['Authorization'] = "Bearer #{@token}" if @token
|
||||||
|
req.body = JSON.generate(query: query)
|
||||||
|
|
||||||
|
JSON.parse(http.request(req).body)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user