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 +4 -4
- data/CHANGELOG.md +10 -0
- data/htb.gemspec +3 -1
- data/lib/htb/cli/machines.rb +28 -7
- data/lib/htb/cli/main.rb +20 -12
- data/lib/htb/cli.rb +1 -1
- data/lib/htb/config.rb +2 -1
- data/lib/htb/machines.rb +30 -25
- data/lib/htb/version.rb +1 -1
- data/lib/htb/vm.rb +2 -2
- data/lib/htb/vpn.rb +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c61e897e5256a77995c2c6b07d1990805855cc59495576e5925d13196192b92
|
|
4
|
+
data.tar.gz: 686ab280f44de1aa5d59ef2b6bd28607b3aefdbd9db3807152f89d3533f22ca5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
7
|
+
spec.version = HTB::VERSION
|
|
6
8
|
spec.authors = ["usiegj00"]
|
|
7
9
|
spec.email = ["usiegj00@users.noreply.github.com"]
|
|
8
10
|
|
data/lib/htb/cli/machines.rb
CHANGED
|
@@ -7,9 +7,11 @@ module HTB
|
|
|
7
7
|
true
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
desc "list", "List
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
["
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
["
|
|
67
|
-
|
|
68
|
-
["
|
|
69
|
-
[
|
|
70
|
-
|
|
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
|
|
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
|
|
10
|
-
# GET /api/v4/machine/
|
|
9
|
+
# List active machines (current season)
|
|
10
|
+
# GET /api/v4/machine/paginated
|
|
11
11
|
def list
|
|
12
|
-
|
|
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
data/lib/htb/vm.rb
CHANGED
data/lib/htb/vpn.rb
CHANGED
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.
|
|
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-
|
|
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.
|
|
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: []
|