diff --git a/.gitignore b/.gitignore index fa300ab..f090514 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.json data/cache/* !data/cache/.gitkeep -_*.* \ No newline at end of file +_*.* +*.log \ No newline at end of file diff --git a/lib/api.rb b/lib/api.rb index 286171d..157a379 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -13,6 +13,20 @@ class W3DHub ENDPOINT = "https://secure.w3dhub.com".freeze + def self.post(url, headers = DEFAULT_HEADERS, body = nil) + @client ||= Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(ENDPOINT, protocol: Async::HTTP::Protocol::HTTP10)) + + # TODO: Check if session has expired and attempt to refresh session before submitting request + + # Inject Authorization header if account data is populated + if Store.account + headers = headers.dup + headers << ["Authorization", "Bearer #{Store.account.access_token}"] + end + + @client.post(url, headers, body) + end + # Method: POST # FORMAT: JSON @@ -28,9 +42,9 @@ class W3DHub # # On a failed login the service responds with: # {"error":"login-failed"} - def self.refresh_user_login(internet, refresh_token) + def self.refresh_user_login(refresh_token) body = "data=#{JSON.dump({refreshToken: refresh_token})}" - response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) + response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) if response.success?#status == 200 user_data = JSON.parse(response.read, symbolize_names: true) @@ -38,7 +52,7 @@ class W3DHub return false if user_data[:error] body = "data=#{JSON.dump({ id: user_data[:userid] })}" - user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) + user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) if user_details.success? user_details_data = JSON.parse(user_details.read, symbolize_names: true) @@ -51,9 +65,9 @@ class W3DHub end # See #user_refresh_token - def self.user_login(internet, username, password) + def self.user_login(username, password) body = "data=#{JSON.dump({username: username, password: password})}" - response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) + response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) if response.success? user_data = JSON.parse(response.read, symbolize_names: true) @@ -61,7 +75,7 @@ class W3DHub return false if user_data[:error] body = "data=#{JSON.dump({ id: user_data[:userid] })}" - user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) + user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) if user_details.success? user_details_data = JSON.parse(user_details.read, symbolize_names: true) @@ -77,14 +91,14 @@ class W3DHub # 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(internetn, id) + def self.user_details(id) end # /apis/w3dhub/1/get-service-status # Service response: # {"services":{"authentication":true,"packageDownload":true}} - def self.service_status(internet) - response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS) + def self.service_status + response = post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS) if response.success? ServiceStatus.new(response.read) @@ -97,8 +111,8 @@ class W3DHub # 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(internet) - response = internet.post("#{ENDPOINT}/apis/launcher/1/get-applications", DEFAULT_HEADERS) + def self.applications + response = post("#{ENDPOINT}/apis/launcher/1/get-applications") if response.success? Applications.new(response.read) @@ -111,11 +125,11 @@ class W3DHub # 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"} ("launcher-home" retrieves the weekly hub updates) # Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one - def self.news(internet, category) + def self.news(category) body = "data=#{JSON.dump({category: category})}" - response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body) + response = post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body) - if response.success?#status == 200 + if response.success? News.new(response.read) else false @@ -126,11 +140,9 @@ class W3DHub # /apis/launcher/1/get-package-details # client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]} - def self.package_details(internet, packages) + def self.package_details(packages) body = URI.encode_www_form("data": JSON.dump({ packages: packages })) - endpoint = Async::HTTP::Endpoint.parse("#{ENDPOINT}/apis/launcher/1/get-package-details", protocol: Async::HTTP::Protocol::HTTP10) - client = Async::HTTP::Client.new(endpoint) - response = client.post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body) + response = post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body) if response.success? hash = JSON.parse(response.read, symbolize_names: true) @@ -144,8 +156,8 @@ class W3DHub # client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"} # # server responds with download bytes, probably supports chunked download and resume - def self.package(internet, package, &block) - Cache.fetch_package(internet, package, block) + def self.package(package, &block) + Cache.fetch_package(package, block) end #! === Server List API === !# diff --git a/lib/api/account.rb b/lib/api/account.rb index ce78dbf..740d1c2 100644 --- a/lib/api/account.rb +++ b/lib/api/account.rb @@ -11,7 +11,7 @@ class W3DHub @username = @data[:username] @displayname = @data[:displayname] - @avatar_uri = user_details[:"avatar-uri"] + @avatar_uri = user_details[:"avatar-uri"] || @data[:avatar_uri] @user_level = @data[:userlevel] @session_token = @data[:"session-token"] @@ -21,6 +21,14 @@ class W3DHub @studio_user_level = @data[:"studio-userlevel"] # Dunno? end + + def to_json(env) + d = @data.dup + d[:avatar_uri] = @avatar_uri + d[:access_token_expiry] = d[:access_token_expiry].to_i + + d.to_json(env) + end end end end diff --git a/lib/application_manager/task.rb b/lib/application_manager/task.rb index 865ec59..42180b7 100644 --- a/lib/application_manager/task.rb +++ b/lib/application_manager/task.rb @@ -325,8 +325,7 @@ class W3DHub } end - internet = Async::HTTP::Internet.instance - package_details = Api.package_details(internet, hashes) + package_details = Api.package_details(hashes) if package_details @packages = [package_details].flatten @@ -515,10 +514,9 @@ class W3DHub def fetch_manifest(category, subcategory, name, version, &block) # Check for and integrity of local manifest - internet = Async::HTTP::Internet.instance package = nil - array = Api.package_details(internet, [{ category: category, subcategory: subcategory, name: name, version: version }]) + array = Api.package_details([{ category: category, subcategory: subcategory, name: name, version: version }]) if array.is_a?(Array) package = array.first else @@ -543,7 +541,7 @@ class W3DHub internet = Async::HTTP::Internet.instance - Api.package(internet, package) do |chunk, remaining_bytes, total_bytes| + Api.package(package) do |chunk, remaining_bytes, total_bytes| block&.call(chunk, remaining_bytes, total_bytes) end end diff --git a/lib/cache.rb b/lib/cache.rb index 393a377..7b780cd 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -7,12 +7,14 @@ class W3DHub end # Fetch a generic uri - def self.fetch(internet, uri, force_fetch = false) + def self.fetch(uri, force_fetch = false) path = path(uri) if !force_fetch && File.exist?(path) path else + internet = Async::HTTP::Internet.instance + response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS) if response.success? @@ -46,9 +48,10 @@ class W3DHub end # Download a W3D Hub package - def self.fetch_package(internet, package, block) + def self.fetch_package(package, block) path = package_path(package.category, package.subcategory, package.name, package.version) headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": Api::USER_AGENT } + headers["Authorization"] = "Bearer #{Store.account.access_token}" if Store.account start_from_bytes = package.custom_partially_valid_at_bytes puts " Start from bytes: #{start_from_bytes} of #{package.size}" diff --git a/lib/pages/community.rb b/lib/pages/community.rb index 5e81158..e9195da 100644 --- a/lib/pages/community.rb +++ b/lib/pages/community.rb @@ -63,18 +63,18 @@ class W3DHub Async do internet = Async::HTTP::Internet.instance - fetch_w3dhub_news(internet) + fetch_w3dhub_news populate_w3dhub_news end end end - def fetch_w3dhub_news(internet) - news = Api.news(internet, "launcher-home") + def fetch_w3dhub_news + news = Api.news("launcher-home") if news news.items[0..9].each do |item| - Cache.fetch(internet, item.image) + Cache.fetch(item.image) end @w3dhub_news = news diff --git a/lib/pages/download_manager.rb b/lib/pages/download_manager.rb index 3c58c77..52d4a80 100644 --- a/lib/pages/download_manager.rb +++ b/lib/pages/download_manager.rb @@ -28,7 +28,8 @@ class W3DHub background task.application.color flow(width: 0.70, height: 1.0) do - @application_image = image "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png", height: 1.0 + image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{task.app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png" + @application_image = image image_path, height: 1.0 stack(margin_left: 8, width: 0.75) do @application_name_label = tagline "#{task.application.name}" diff --git a/lib/pages/games.rb b/lib/pages/games.rb index e31baef..3a6984b 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -4,7 +4,9 @@ class W3DHub def setup @game_news ||= {} @focused_game ||= Store.applications.games.find { |g| g.id == Store.settings[:last_selected_app] } + @focused_game ||= Store.applications.games.find { |g| g.id == "ren" } @focused_channel ||= @focused_game.channels.find { |c| c.id == Store.settings[:last_selected_channel] } + @focused_channel ||= @focused_game.channels.first body.clear do # Games List @@ -38,7 +40,9 @@ class W3DHub image "#{GAME_ROOT_PATH}/media/ui_icons/return.png", width: 1.0, color: Gosu::Color::GRAY if Store.application_manager.updateable?(game.id, game.channels.first.id) image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 0.5, color: 0x88_ffffff unless Store.application_manager.installed?(game.id, game.channels.first.id) end - image "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png", height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff + image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{game.id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png" + + image image_path, height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff end inscription game.name, width: 1.0, text_align: :center end @@ -168,20 +172,18 @@ class W3DHub end Async do - internet = Async::HTTP::Internet.instance - - fetch_game_news(internet, game) + fetch_game_news(game) populate_game_news(game) end end end - def fetch_game_news(internet, game) - news = Api.news(internet, game.id) + def fetch_game_news(game) + news = Api.news(game.id) if news news.items[0..9].each do |item| - Cache.fetch(internet, item.image) + Cache.fetch(item.image) end @game_news[game.id] = news diff --git a/lib/pages/login.rb b/lib/pages/login.rb index f07a261..96c4ca7 100644 --- a/lib/pages/login.rb +++ b/lib/pages/login.rb @@ -37,18 +37,20 @@ class W3DHub # Do network stuff Async do - internet = Async::HTTP::Internet.instance - - account = Api.user_login(internet, @username.value, @password.value) + account = Api.user_login(@username.value, @password.value) if account Store.account = account - Store.settings[:account][:refresh_token] = account.refresh_token + Store.settings[:account][:data] = account Store.settings.save_settings - Cache.fetch(internet, account.avatar_uri, true) + internet = Async::HTTP::Internet.instance + Cache.fetch(account.avatar_uri, true) populate_account_info + applications = Api.applications + Store.applications = applications if applications + page(W3DHub::Pages::Games) else # An error occurred, enable account entry @@ -70,8 +72,7 @@ class W3DHub if Store.account Async do - internet = Async::HTTP::Internet.instance - Cache.fetch(internet, Store.account.avatar_uri) + Cache.fetch(Store.account.avatar_uri) populate_account_info page(W3DHub::Pages::Games) @@ -98,10 +99,13 @@ class W3DHub end def depopulate_account_info - Store.settings[:account][:refresh_token] = nil + Store.settings[:account] = {} Store.settings.save_settings Store.account = nil + applications = Api.applications + Store.applications if applications + @host.instance_variable_get(:"@account_container").clear do stack(width: 0.7, height: 1.0) do # background 0xff_222222 diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index f14dd07..0f3f219 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -26,9 +26,11 @@ class W3DHub @filters.each do |app_id, enabled| app = Store.applications.games.find { |a| a.id == app_id.to_s } - image "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png", tip: "#{app.name}", height: 1.0, + image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png" + + image image_path, tip: "#{app.name}", height: 1.0, border_thickness_bottom: 1, border_color_bottom: 0x00_000000, - color: enabled ? 0xff_ffffff : 0xff_444444, hover: { border_color_bottom: 0xff_aaaaaa }, margin_right: 32 do |img| + color: enabled ? 0xff_ffffff : 0xff_444444, hover: { border_color_bottom: 0xff_aaaaaa }, margin_right: 16 do |img| @filters[app_id] = !@filters[app_id] Store.settings[:server_list_filters] = @filters Store.settings.save_settings @@ -359,9 +361,11 @@ class W3DHub end def game_icon(server) + image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" : "#{GAME_ROOT_PATH}/media/icons/app.png" + if server.status.password @server_locked_icons[server.game] ||= Gosu.render(96, 96) do - i = get_image("#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png") + i = get_image(image_path) lock = get_image("#{GAME_ROOT_PATH}/media/ui_icons/locked.png") scale = [96.0 / i.width, 96.0 / i.height].min @@ -369,7 +373,7 @@ class W3DHub lock.draw(96 - lock.width * 0.5, 96 - lock.height * 0.5, 0, 0.5, 0.5, 0xff_ff8800) end else - "#{GAME_ROOT_PATH}/media/icons/#{server.game.nil? ? 'ren' : server.game}.png" + image_path end end diff --git a/lib/states/boot.rb b/lib/states/boot.rb index c477045..82d22e0 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -32,11 +32,9 @@ class W3DHub end Async do - internet = Async::HTTP::Internet.instance - @tasks.keys.each do |key| Sync do - send(key, internet) + send(key) end end end @@ -56,26 +54,29 @@ class W3DHub @progressbar.value = @fraction - if @progressbar.value >= 1.0 && @task_index == @tasks.size - Store.account = @account - Store.service_status = @service_status - Store.applications = @applications - - push_state(States::Interface) - end + push_state(States::Interface) if @progressbar.value >= 1.0 && @task_index == @tasks.size @task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete) end - def refresh_user_token(internet) - if Store.settings[:account, :refresh_token] - @account = Api.refresh_user_login(internet, Store.settings[:account, :refresh_token]) + def refresh_user_token + if Store.settings[:account, :data] + account = Api::Account.new(Store.settings[:account, :data], {}) + + if (Time.now.to_i - account.access_token_expiry.to_i) >= 60 * 3 # Older than 3 hours then refresh + @account = Api.refresh_user_login(account.refresh_token) + else + @account = account + end if @account - Store.settings[:account][:refresh_token] = @account.refresh_token - Cache.fetch(internet, @account.avatar_uri, true) + Store.account = @account + + Store.settings[:account][:data] = @account + + Cache.fetch(@account.avatar_uri, true) else - Store.settings[:account][:refresh_token] = nil + Store.settings[:account] = {} end Store.settings.save_settings @@ -86,10 +87,12 @@ class W3DHub end end - def service_status(internet) - @service_status = Api.service_status(internet) + def service_status + @service_status = Api.service_status if @service_status + Store.service_status = @service_status + if !@service_status.authentication? || !@service_status.package_download? @status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}." end @@ -100,22 +103,26 @@ class W3DHub end end - def applications(internet) + def applications @status_label.value = I18n.t(:"boot.checking_for_updates") - @applications = Api.applications(internet) + @applications = Api.applications if @applications + Store.applications = @applications + @tasks[:applications][:complete] = true else # FIXME: Failed to retreive! end end - def server_list(internet) + def server_list @status_label.value = I18n.t(:"server_browser.fetching_server_list") begin + internet = Async::HTTP::Internet.instance + list = Api.server_list(internet, 2) if list