diff --git a/lib/api.rb b/lib/api.rb new file mode 100644 index 0000000..5f9e8a0 --- /dev/null +++ b/lib/api.rb @@ -0,0 +1,137 @@ +class W3DHub + class Api + USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}" + DEFAULT_HEADERS = { + "User-Agent": USER_AGENT + } + + #! === W3D Hub API === !# + + ENDPOINT = "https://secure.w3dhub.com" + W3DHUB_API_CONNECTION = Excon.new(ENDPOINT, persistent: true, connect_timeout: 15) + # Method: POST + # FORMAT: JSON + + # /apis/launcher/1/user-login + # For an already logged in user the launcher sends + # a "refreshToken" in the data field: data={"refreshToken":"TOKEN_STRING"} + # + # For a logging in user the launcher sends + # data={"username":"NAME","password":"password_as_plaintext_but_over_https"} + # + # On successful login/token refresh the service responds with: + # {"session_token":"string","userid:"1234"...} + # + # On a failed login the service responds with: + # {"error":"login-failed"} + def self.user_refresh_token(refresh_token) + end + + # See #user_refresh_token + def self.user_login(username, password) + end + + # /apis/launcher/1/user-login + # Client sends an Authorization header bearer token which is received from logging in (Required?) + # + # Response: avatar-uri (Image download uri), id, username + def self.user_details(id) + end + + # /apis/w3dhub/1/get-service-status + # Service response: + # {"services":{"authentication":true,"packageDownload":true}} + def self.service_status + response = W3DHUB_API_CONNECTION.post( + path: "apis/w3dhub/1/get-service-status", + headers: DEFAULT_HEADERS + ) + + if response.status == 200 + ServiceStatus.new(response.body) + else + false + end + end + + # /apis/launcher/1/get-applications + # Client sends an Authorization header bearer token which is received from logging in (Optional) + # Launcher sends an empty data request: data={} + # Response is a list of applications/games + def self.applications + response = W3DHUB_API_CONNECTION.post( + path: "apis/launcher/1/get-applications", + headers: DEFAULT_HEADERS + ) + + if response.status == 200 + Applications.new(response.body) + else + false + end + end + + # /apis/w3dhub/1/get-news + # Client sends an Authorization header bearer token which is received from logging in (Optional) + # Client requests news for a specific application/game e.g.: data={"category":"ia"} + # Response is a JSON hash with a "highlighted" and "news" keys; the "news" on seems to be the desired one + def self.news(category) + end + + # Downloading games + + # /apis/launcher/1/get-package-details + # client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]} + def self.package_details() + end + + # /apis/launcher/1/get-package + # client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"} + # + # server responds with download bytes + def self.package(category, name, subcategory, version, &block) + end + + #! === Server List API === !# + + SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com" + # Method: GET + # FORMAT: JSON + + # /listings/getAll/v2?statusLevel=#{0-2} + # statusLevel = 0 returns: + # id, game, address, port, and region + # statusLevel = 1 returns: (This is the default for the Launcher) + # id, game, address, port, region, and status: + # name, map, maxplayers, numplayers, started (DateTime), and remaining (RenTime) + # statusLevel = 2 returns: + # id, game, address, port, region, status: + # name, map, maxplayers, numplayers, started (DateTime), and remaining (RenTime) + # ...teams[]: + # id, name, score, kills, deaths + # ...players[]: + # nick, team (index of teams array), score, kills, deaths + def self.server_list(level = 1) + end + + # /listings/getStatus/v2/:id?statusLevel=#{0-2} + # statusLevel = 0 returns: + # Empty/Blank response, assume 500 or 400 error + # statusLevel = 1 returns: + # name, map, maxplayers, numplayers, started (DateTime), remaining (RenTime) + # statusLevel = 2 returns: + # name, map, maxplayers, numplayers, started (DateTime), remaining (RenTime) + # ...teams[]: + # id, name, score, kills, deaths + # ...players[]: + # nick, team (index of teams array), score, kills, deaths + def self.server_details(id, level) + end + + # /listings/push/v2/negotiate?negotiateVersion=1 + ##? /listings/push/v2/?id=#{websocket token?} + ## Websocket server list listener + def self.server_list_push(id) + end + end +end diff --git a/lib/api/applications.rb b/lib/api/applications.rb new file mode 100644 index 0000000..1c5ac2f --- /dev/null +++ b/lib/api/applications.rb @@ -0,0 +1,64 @@ +class W3DHub + class Api + class Applications + def initialize(response) + @data = JSON.parse(response, symbolize_names: true) + + games = @data[:applications].select { |a| a[:category] == "games" } + + @games = [] + + games.each { |hash| @games << Game.new(hash) } + end + + def games + @games + end + + class Game + attr_reader :id, :name, :type, :category, :studio_id, :channels, :web_links, :color + + def initialize(hash) + @data = hash + + @id = @data[:id] + @name = @data[:name] + @type = @data[:type] + @category = @data[:category] + @studio_id = @data[:"studio-id"] + + # TODO: Do processing + @channels = @data[:channels] + @web_links = @data[:"web-links"] + @extended_data = @data[:"extended-data"] + + color = @data[:"extended-data"].find { |h| h[:name] == "colour" }[:value].sub("#", "") + + @color = "ff#{color}".to_i(16) + end + + class Channel + def initialize(hash) + @data = hash + + @id = @data[:id] + @name = @data[:name] + @user_level = @data[:"user-level"] + @current_version = @data[:"current-version"] + end + end + + class WebLink + attr_reader :name, :uri + + def initialize(hash) + @data = hash + + @name = hash[:name] + @uri = hash[:uri] + end + end + end + end + end +end diff --git a/lib/api/service_status.rb b/lib/api/service_status.rb new file mode 100644 index 0000000..502c72f --- /dev/null +++ b/lib/api/service_status.rb @@ -0,0 +1,17 @@ +class W3DHub + class Api + class ServiceStatus + def initialize(response) + @data = JSON.parse(response, symbolize_names: true) + end + + def authentication? + @data[:services][:authentication] + end + + def package_download? + @data[:services][:packageDownload] + end + end + end +end diff --git a/lib/states/boot.rb b/lib/states/boot.rb index 8c95bb6..408e626 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -6,6 +6,13 @@ class W3DHub @fraction = 0.0 @w3dhub_logo = get_image("#{GAME_ROOT_PATH}/media/icons/w3dhub.png") + @tasks = { + refresh_user_token: { started: false, complete: false }, + service_status: { started: false, complete: false }, + applications: { started: false, complete: false } + } + + @task_index = 0 stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: 0xff_aaaaaa) do stack(width: 1.0, height: 0.925) do @@ -14,7 +21,7 @@ class W3DHub @progressbar = progress height: 0.025, width: 1.0, fraction_background: 0xff_00acff, border_thickness: 0 flow(width: 1.0, height: 0.05, padding_left: 16, padding_right: 16, padding_bottom: 8, padding_top: 8) do - caption "Checking for updates...", width: 0.5 + @status_label = caption "Starting #{NAME}...", width: 0.5 inscription "W3D Hub Launcher 0.14.0.0", width: 0.5, text_align: :right end end @@ -29,11 +36,70 @@ class W3DHub def update super - @fraction += 1.0 * window.dt + # @fraction += 1.0 * window.dt + @fraction = 1.0 / (@tasks.size / @task_index.to_f) @progressbar.value = @fraction - push_state(States::Interface) if @progressbar.value >= 1.0 + if @progressbar.value >= 1.0 && @task_index == @tasks.size + push_state( + States::Interface, + refresh_token: @refresh_token, + service_status: @service_status, + applications: @applications + ) + end + + send(:"#{@tasks.keys[@task_index]}") if @tasks.dig(@tasks.keys[@task_index], :complete) == false + + @task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete) + end + + def refresh_user_token + @tasks[:refresh_user_token][:started] = true + @tasks[:refresh_user_token][:complete] = true + + @refresh_token = nil + end + + def service_status + @tasks[:service_status][:started] = true + + Thread.new do + @service_status = Api.service_status + + if service_status + if !service_status.authentication? || !service_status.package_download? + # FIXME: MAIN THREAD! + @status_label.value = "Authentication is #{service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{service_status.package_download? ? 'Okay' : 'Down'}." + end + + @tasks[:service_status][:complete] = true + else + # FIXME: MAIN THREAD! + @status_label.value = "W3D Hub Service is down." + end + end + end + + def applications + @status_label.value = "Checking for updates..." + + warn "ALREADY STARTED APPLICATIONS!!!" if @tasks[:applications][:started] + + return if @tasks[:applications][:started] + + @tasks[:applications][:started] = true + + Thread.new do + @applications = Api.applications + + if applications + pp applications.games.first + + @tasks[:applications][:complete] = true + end + end end end end diff --git a/lib/states/interface.rb b/lib/states/interface.rb index 21bcd95..04da09e 100644 --- a/lib/states/interface.rb +++ b/lib/states/interface.rb @@ -2,10 +2,15 @@ class W3DHub class States class Interface < CyberarmEngine::GuiState attr_reader :main_thread_queue + attr_accessor :refresh_token, :service_status, :applications def setup window.show_cursor = true + @refresh_token = @options[:refresh_token] + @service_status = @options[:service_status] + @applications = @options[:applications] + @page = nil @pages = {} diff --git a/lib/version.rb b/lib/version.rb new file mode 100644 index 0000000..8098436 --- /dev/null +++ b/lib/version.rb @@ -0,0 +1,4 @@ +class W3DHub + NAME = "W3D Hub Launcher" + VERSION = "0.1.0" +end \ No newline at end of file diff --git a/w3dhub.rb b/w3dhub.rb index ed5a5b1..0d9ba0f 100644 --- a/w3dhub.rb +++ b/w3dhub.rb @@ -8,10 +8,15 @@ GAME_ROOT_PATH = File.expand_path(".", __dir__) EMPTY_IMAGE = Gosu::Image.from_blob(1, 1) BLACK_IMAGE = Gosu::Image.from_blob(1, 1, "\x00\x00\x00\xff") +require_relative "lib/version" require_relative "lib/window" require_relative "lib/states/boot" require_relative "lib/states/interface" +require_relative "lib/api" +require_relative "lib/api/service_status" +require_relative "lib/api/applications" + require_relative "lib/game" require_relative "lib/games/renegade" require_relative "lib/games/expansive_civilian_warfare"