htb 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e6caea0b99b8c05235a7d882577af1a106b5313efdbe8e8034bdd18a18f1c83
4
- data.tar.gz: d8e9addfbe08acc7acaaf30d5a033871b93b9271bcc93a0d85dd2bd33c914da5
3
+ metadata.gz: 1c61e897e5256a77995c2c6b07d1990805855cc59495576e5925d13196192b92
4
+ data.tar.gz: 686ab280f44de1aa5d59ef2b6bd28607b3aefdbd9db3807152f89d3533f22ca5
5
5
  SHA512:
6
- metadata.gz: 053eb8609fd604dd1921c30279a686bc86d4362f9e4756e518168a505deeb720210d75aea4e2d84ddedf8510da71e49a036e7076560f304ab19076a2a96f3787
7
- data.tar.gz: 853a12f640c67d268078cbf8fbf540bf5d7875d186ee8c1b0896d58db2b3f1f0f64b5c58bfcc3d4702fae675a2b902c401d631bdbd9c53cc71be2b2d94294e81
6
+ metadata.gz: 1ff67688442edf583e9d1b81961199e078ebfa9425efc981c23e254af7cc26ddfd08fcfffca5dac4f3eea380cac20c8bf55720fdf4bf5109428d1c949243de25
7
+ data.tar.gz: e1a7c7efb65dbff32c972a040c83db23041665d7df32f610c8497f209bfe93ef933f32ec3f7a451b5fae4fadc96f37e0e909bad83c8f6e007b2c2fd45d81ca8c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.1] - 2026-01-17
9
+
10
+ ### Fixed
11
+
12
+ - Fixed VPN status endpoint from `/connections/status` to `/connection/status`
13
+ - Fixed VPN status response handling to properly parse Array response
14
+ - Fixed VM active endpoint from `/vm/active` to `/machine/active`
15
+ - Fixed config file YAML parsing to handle non-Hash responses gracefully
16
+ - Changed "Machine" label to "Challenge" in status output
17
+
8
18
  ## [0.1.0] - 2025-01-17
9
19
 
10
20
  ### Added
data/htb.gemspec CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "lib/htb/version"
4
+
3
5
  Gem::Specification.new do |spec|
4
6
  spec.name = "htb"
5
- spec.version = "0.1.0"
7
+ spec.version = HTB::VERSION
6
8
  spec.authors = ["usiegj00"]
7
9
  spec.email = ["usiegj00@users.noreply.github.com"]
8
10
 
@@ -7,9 +7,11 @@ module HTB
7
7
  true
8
8
  end
9
9
 
10
- desc "list", "List all active machines"
10
+ desc "list", "List machines"
11
11
  option :retired, type: :boolean, desc: "Show retired machines instead"
12
12
  option :sp, type: :boolean, desc: "Show Starting Point machines"
13
+ option :unsolved, type: :boolean, desc: "Show only unsolved machines"
14
+ option :difficulty, type: :string, desc: "Filter by difficulty (Easy, Medium, Hard, Insane)"
13
15
  def list
14
16
  client = CLI.client
15
17
 
@@ -24,23 +26,42 @@ module HTB
24
26
  end
25
27
 
26
28
  if machines && machines["info"]
27
- rows = machines["info"].map do |m|
29
+ machine_list = machines["info"]
30
+
31
+ if options[:unsolved]
32
+ machine_list = machine_list.reject do |m|
33
+ m["authUserInUserOwns"] && m["authUserInRootOwns"]
34
+ end
35
+ end
36
+
37
+ if options[:difficulty]
38
+ diff = options[:difficulty].downcase
39
+ machine_list = machine_list.select do |m|
40
+ (m["difficultyText"] || "").downcase == diff
41
+ end
42
+ end
43
+
44
+ rows = machine_list.map do |m|
28
45
  difficulty = m["difficultyText"] || m["difficulty"] || "N/A"
29
46
  os = m["os"] || "N/A"
47
+ user_own = m["authUserInUserOwns"] ? CLI.pastel.green("Y") : CLI.pastel.red("-")
48
+ root_own = m["authUserInRootOwns"] ? CLI.pastel.green("Y") : CLI.pastel.red("-")
30
49
  [
31
50
  m["id"],
32
51
  m["name"],
33
52
  os,
34
53
  difficulty,
35
- m["user_owns_count"] || 0,
36
- m["root_owns_count"] || 0,
37
- m["release"] || "N/A"
54
+ user_own,
55
+ root_own,
56
+ m["release"]&.slice(0, 10) || "N/A"
38
57
  ]
39
58
  end
40
59
 
41
- puts CLI.pastel.bold("\nMachines:")
60
+ label = options[:retired] ? "Retired" : options[:sp] ? "Starting Point" : "Active"
61
+ label += " (unsolved)" if options[:unsolved]
62
+ puts CLI.pastel.bold("\n#{label} Machines:")
42
63
  CLI.print_table(
43
- ["ID", "Name", "OS", "Difficulty", "User Owns", "Root Owns", "Released"],
64
+ ["ID", "Name", "OS", "Difficulty", "User", "Root", "Released"],
44
65
  rows
45
66
  )
46
67
  puts "\nTotal: #{rows.size} machines"
data/lib/htb/cli/main.rb CHANGED
@@ -44,7 +44,7 @@ module HTB
44
44
  CLI.print_table(
45
45
  ["Field", "Value"],
46
46
  [
47
- ["Machine", info["name"] || "N/A"],
47
+ ["Challenge", info["name"] || "N/A"],
48
48
  ["IP", info["ip"] || "N/A"],
49
49
  ["Type", info["type"] || "N/A"],
50
50
  ["Expires", info["expires_at"] || "N/A"]
@@ -60,18 +60,26 @@ module HTB
60
60
  puts CLI.pastel.bold("\n=== VPN Status ===")
61
61
  begin
62
62
  vpn_status = CLI.spinner("Fetching VPN status...") { client.vpn.status }
63
- if vpn_status && vpn_status["data"]
64
- data = vpn_status["data"]
65
- CLI.print_table(
66
- ["Field", "Value"],
67
- [
68
- ["Connected", data["connection"] ? "Yes" : "No"],
69
- ["Server", data["server"] || "N/A"],
70
- ["IP", data["ip"] || "N/A"]
71
- ]
72
- )
63
+ if vpn_status.is_a?(Array) && !vpn_status.empty?
64
+ vpn_status.each do |conn|
65
+ server = conn["server"] || {}
66
+ connection = conn["connection"] || {}
67
+ CLI.print_table(
68
+ ["Field", "Value"],
69
+ [
70
+ ["Type", conn["type"] || "N/A"],
71
+ ["Location", conn["location_type_friendly"] || "N/A"],
72
+ ["Server", server["friendly_name"] || "N/A"],
73
+ ["Connected", connection["name"] ? "Yes" : "No"],
74
+ ["IPv4", connection["ip4"] || "N/A"],
75
+ ["IPv6", connection["ip6"] || "N/A"],
76
+ ["Down", connection["down"] ? "#{connection['down']} KB/s" : "N/A"],
77
+ ["Up", connection["up"] ? "#{connection['up']} KB/s" : "N/A"]
78
+ ]
79
+ )
80
+ end
73
81
  else
74
- CLI.print_info("VPN status unavailable")
82
+ CLI.print_info("No active VPN connections")
75
83
  end
76
84
  rescue HTB::Error => e
77
85
  CLI.print_error(e.message)
data/lib/htb/cli.rb CHANGED
@@ -6,12 +6,12 @@ require "tty-spinner"
6
6
  require "tty-prompt"
7
7
  require "pastel"
8
8
 
9
- require_relative "cli/main"
10
9
  require_relative "cli/machines"
11
10
  require_relative "cli/vm"
12
11
  require_relative "cli/users"
13
12
  require_relative "cli/challenges"
14
13
  require_relative "cli/vpn"
14
+ require_relative "cli/main"
15
15
 
16
16
  module HTB
17
17
  module CLI
data/lib/htb/config.rb CHANGED
@@ -12,7 +12,8 @@ module HTB
12
12
  return {} unless File.exist?(CONFIG_FILE)
13
13
 
14
14
  content = File.read(CONFIG_FILE)
15
- YAML.safe_load(content, symbolize_names: true) || {}
15
+ result = YAML.safe_load(content, symbolize_names: true)
16
+ result.is_a?(Hash) ? result : {}
16
17
  rescue StandardError
17
18
  {}
18
19
  end
data/lib/htb/machines.rb CHANGED
@@ -6,10 +6,16 @@ module HTB
6
6
  @client = client
7
7
  end
8
8
 
9
- # List all machines
10
- # GET /api/v4/machine/list
9
+ # List active machines (current season)
10
+ # GET /api/v4/machine/paginated
11
11
  def list
12
- @client.get("/machine/list")
12
+ paginate("/machine/paginated")
13
+ end
14
+
15
+ # List all retired machines (full VIP+ back catalog)
16
+ # GET /api/v4/machine/list/retired/paginated
17
+ def retired
18
+ paginate("/machine/list/retired/paginated")
13
19
  end
14
20
 
15
21
  # Get machine profile by ID or name
@@ -38,9 +44,6 @@ module HTB
38
44
 
39
45
  # Submit flag for machine ownership
40
46
  # POST /api/v4/machine/own
41
- # @param machine_id [Integer] Machine ID
42
- # @param flag [String] The flag to submit
43
- # @param difficulty [Integer] Difficulty rating (10-100)
44
47
  def own(machine_id:, flag:, difficulty: 50)
45
48
  @client.post("/machine/own", {
46
49
  id: machine_id,
@@ -87,10 +90,6 @@ module HTB
87
90
 
88
91
  # Submit machine review
89
92
  # POST /api/v4/machine/review
90
- # @param machine_id [Integer] Machine ID
91
- # @param stars [Integer] Star rating (1-5)
92
- # @param headline [String] Review headline
93
- # @param review [String] Review text
94
93
  def review(machine_id:, stars:, headline:, review:)
95
94
  @client.post("/machine/review", {
96
95
  id: machine_id,
@@ -112,31 +111,37 @@ module HTB
112
111
  @client.get("/machine/spawned")
113
112
  end
114
113
 
115
- # Get retired machines
116
- # GET /api/v4/machine/list/retired
117
- def retired
118
- @client.get("/machine/list/retired")
119
- end
120
-
121
114
  # Get starting point machines
122
115
  # GET /api/v4/machine/list/sp
123
116
  def starting_point
124
117
  @client.get("/machine/list/sp")
125
118
  end
126
119
 
127
- # Get paginated machine list
128
- # GET /api/v4/machine/paginated
129
- def paginated(page: 1, per_page: 20, sort_by: nil, sort_type: nil)
130
- params = { page: page, per_page: per_page }
131
- params[:sort_by] = sort_by if sort_by
132
- params[:sort_type] = sort_type if sort_type
133
- @client.get("/machine/paginated", params)
134
- end
135
-
136
120
  # Search machines
137
121
  # GET /api/v4/search/fetch?query={query}&tags=[]
138
122
  def search(query, tags: [])
139
123
  @client.get("/search/fetch", { query: query, tags: tags })
140
124
  end
125
+
126
+ private
127
+
128
+ # Fetch all pages from a paginated endpoint and return unified hash
129
+ # with "info" key for CLI compatibility
130
+ def paginate(path)
131
+ all_machines = []
132
+ page = 1
133
+ loop do
134
+ result = @client.get(path, { page: page, per_page: 100 })
135
+ data = result["data"] || []
136
+ break if data.empty?
137
+
138
+ all_machines.concat(data)
139
+ last_page = result.dig("meta", "last_page") || 1
140
+ break if page >= last_page
141
+
142
+ page += 1
143
+ end
144
+ { "info" => all_machines }
145
+ end
141
146
  end
142
147
  end
data/lib/htb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTB
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/htb/vm.rb CHANGED
@@ -49,9 +49,9 @@ module HTB
49
49
  end
50
50
 
51
51
  # Get active VM
52
- # GET /api/v4/vm/active
52
+ # GET /api/v4/machine/active
53
53
  def active
54
- @client.get("/vm/active")
54
+ @client.get("/machine/active")
55
55
  end
56
56
 
57
57
  # Transfer VM to another lab
data/lib/htb/vpn.rb CHANGED
@@ -13,9 +13,9 @@ module HTB
13
13
  end
14
14
 
15
15
  # Get current VPN connection status
16
- # GET /api/v4/connections/status
16
+ # GET /api/v4/connection/status
17
17
  def status
18
- @client.get("/connections/status")
18
+ @client.get("/connection/status")
19
19
  end
20
20
 
21
21
  # Switch VPN server
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: htb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - usiegj00
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-17 00:00:00.000000000 Z
11
+ date: 2026-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -203,7 +203,7 @@ metadata:
203
203
  homepage_uri: https://github.com/aluminumio/htb-ruby
204
204
  source_code_uri: https://github.com/aluminumio/htb-ruby
205
205
  changelog_uri: https://github.com/aluminumio/htb-ruby/blob/main/CHANGELOG.md
206
- post_install_message:
206
+ post_install_message:
207
207
  rdoc_options: []
208
208
  require_paths:
209
209
  - lib
@@ -218,8 +218,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
218
  - !ruby/object:Gem::Version
219
219
  version: '0'
220
220
  requirements: []
221
- rubygems_version: 3.0.3.1
222
- signing_key:
221
+ rubygems_version: 3.5.22
222
+ signing_key:
223
223
  specification_version: 4
224
224
  summary: Ruby client for the Hack The Box API v4
225
225
  test_files: []